I have been evaluating the use of Scott Mitchell's RSSFeed control for a new version of my website (it's not done yet) for my consulting company that focuses on the Cable Sector of the Telecommunications Industry and I wanted to display relevant information for both Web Services and the Cable Sector. The control is very nice (and free). During my investigation I wanted to perform a couple of actions that were not quickly apparent but still quite doable once I dug a bit deeper. Specifically, I wanted to be able to:
- Keep all items to a single row in the control (without wordwrap).
- Display ToolTips
To put all items in a single row, I noticed that it has a boolean called 'Wrap' for an ItemStyle; however, I could not get this to work so I took a nother approach. I modified the RSSDocument to shorten the title to display it correctly. Here is how I did it:
First the code to wire it up:
cableNewsFeed.DataSource = getList( [DataSource], __maxTitleLength, cableNewsFeed.MaxItems );
cableNewsFeed.DataBind();
Next the code to create the list:
protected RssDocument getList( string dataSource, int maxTitleLength, int maxFeedLength )
{
// create the engine and get the xml document
RssEngine engine = new RssEngine();
RssDocument sourceDocument = engine.GetDataSource( dataSource );
// all of the properties on the document are 'Get' only, so I created a copy.
RssDocument results = new RssDocument( sourceDocument.Title, sourceDocument.Link,
sourceDocument.Description, sourceDocument.Version, sourceDocument.FeedType,
sourceDocument.PubDate);
// get the max offset ( i pass it this in to the method based on the max items of control).
int countOffset = sourceDocument.Items.Count-1;
for( int i = 0; i < maxFeedLength && countOffset >= i; i++ )
{
// get the item to clone
RssItem item = sourceDocument.Items[i];
// shorten the title.
string title = getTitle( item.Title, maxTitleLength );
// add the 'New' item.
results.Items.Add( new RssItem( title, item.Link, item.Description,
item.Author, item.Category, item.Guid, item.PubDate, item.RssEnclosure ) );
}
// return the results.
return results;
}
Method to create the title:
protected string getTitle( string title, int maxLength )
{
int length = title.Length;
if( length > maxLength ) title = title.Substring(0,maxLength);
return title += "...";
}
If I were really industrious I would have created the method based on the GDI+ MeasureString method. However, I am not guarenteed that everyone will have the font on their machine. I still think it is worthy of doing this, but maybe later.
Next, I wanted the tool tip to display the description of the RSSFeed. To do this, I wired up the ItemDataBound event:
cableNewsFeed.ItemDataBound+=
new RssFeedItemEventHandler(cableNewsFeed_ItemDataBound); Now the method:
private void cableNewsFeed_ItemDataBound(object sender, RssFeedItemEventArgs e)
{
( ( System.Web.UI.WebControls.WebControl )e.Item).ToolTip = e.Item.DataItem.Description;
}
The results (btw, it will probably be under a different company name with different verbage):
Very recently I came across an issue that required the creation of a new class derived from XmlSerializer. For reasons I don't want to get into here, we serialize an object instance into XML and store it into a database column so that we can reconstitute it later. This is a great approach except for the issue of changing class definitions.
Lastly, if you are just changing the definition of a top level class, then I suggest taking a look at XmlAttributeOverrides on msdn; however, if you are changing the definition of a class that aggregates other classes and one of your contained class has a different class definition, you need to look at using:
XmlAttributeEventHandler, XmlElementEventHandler or XmlNodeEventHandler
These events allows you to control the creation of these internal aggregated classes.
For example, if you have the following class definition for ItemOption that contains a collection of objects of type ProductAttribute and your ProductAttribute definition has changed, then this is a good candidate for creating your own XmlSerializer derived class.
(Simplified For Brevity)
[Serializable]
public class ItemOption
{
protected string _name=null;
protected string _description=null;
protected ProductAttributeCollection _generalAttributes=new ProductAttributeCollection();
public ItemOption(){}
#region properties
[XmlElement ("Name")]
public string Name
{
get{return _name;}
set{_name=value;}
}
[XmlElement ("Description")]
public string Description
{
get{return _description;}
set{_description=value;}
}
[XmlArray("GeneralAttributes")]
[XmlArrayItem("Attribute")]
public ProductAttributeCollection GeneralAttributes
{
get{return _generalAttributes;}
set{_generalAttributes=value;}
}
}
For the purposes of brevity, let's say that all you did was change your ProductAttribute definition from using XmlAttribute to XmlElement.For example:
[Serializable] public abstract class ProductAttribute
{
#region Member Variables
protected string _name = null;
protected object _value;
protected bool _canOverride = false;
#endregion
[XmlElement("Name")] <-- Used to be XmlAttribute
public string Name
{
get{return _name;}
set{_name = value;}
}
[XmlIgnore()]
public object Value
{
get{return _value;}
set{_value = value;}
}
[XmlElement("CanOverride")] <-- Used to be XmlAttribute
public bool CanOverride
{
get{return _canOverride;}
set{_canOverride = value;}
}
public ProductAttribute(){}
public ProductAttribute(int id, string name, bool canOverride, object attributeValue)
{
_id = id;
_name = name;
_value = attributeValue;
_canOverride = canOverride;
}
}
If you make this seemingly innocuous change, your code will no longer work as expected. In this particular example, the deserialization process will NOT throw an exception and it will fill ItemOption.ProductAttributeCollection with the correct # of ProductAttributes; however, each ProductAttribute definition will contain their default values and NOT the values stored in your RDBMS.
The reason is that the Xml stream will contain XmlAttributes and your class definition is expecting XmlElements. This also means that you will have data stored that correspond to two different versions of your class structure. To resolve this issue, I suggest creating your own XmlSerializer class. You want to do this because XmlSerializer will notify you when it encounters an unknown node/element or attribute. You just need to wire it up.
For example, I chose to use the XmlNodeEventHandler and to wire it up, you merely need to:
UnknownNode += new XmlNodeEventHandler(_unknownNode);
Then you need to create the _unknownNode f(x) to handle these events. In the following example, I have other derived classes from ProductAttribute and I omitted some of the method code for brevity, but the example illustrates how to handle this:
protected void _unknownNode(object sender, XmlNodeEventArgs e)
{
object o = e.ObjectBeingDeserialized;
if (o is ProductAttribute)
{
ProductAttribute productAttribute = (ProductAttribute)o;
switch (e.Name)
{
case "xsi:type":
break;
case "Name":
productAttribute.Name = e.Text;
break;
case "CanOverride":
productAttribute.CanOverride = Convert.ToBoolean(e.Text);
break;
default:
if (o is BooleanAttribute && e.Name == "Value")
productAttribute.Value = Convert.ToBoolean(e.Text);
else if (o is DoubleAttribute && e.Name == "Value")
productAttribute.Value = Convert.ToDouble(e.Text);
else if (o is IntegerAttribute && e.Name == "Value")
productAttribute.Value = Convert.ToInt32(e.Text);
else if (o is LongAttribute && e.Name == "Value")
productAttribute.Value = Convert.ToInt64(e.Text);
else if (o is PresentationAttribute && e.Name == "Value")
productAttribute.Value = e.Text;
else if (o is LocationAttribute && e.Name == "Value")
productAttribute.Value = e.Text;
else if (o is ImageAttribute && e.Name == "Value")
productAttribute.Value = e.Text;
else if (o is StringAttribute && e.Name == "Value")
productAttribute.Value = e.Text;
break;
}
}
}
The key thing to remember is that the property o.ObjectBeingDeserialized contains a pointer to the object having difficulty with the Deserialization process. In the example above, you just cast it and set the correct items depending upon the other properties contained in XmlNodeEventArgs. Also, you will noticed that I have a check for xsi:type that does nothing. I did this because even if your class definition and the Xml jibe, the XmlSerializer does not recognize this attribute name and will raise the event. Therefore, I want it to break out of the method as soon as possible.
Hope this helps
Mathew Nolton