(Υποψιάζομαι ότι είτε έγραψες όλο τον κώδικα μόνος σου από το μηδέν, ή χρησιμοποιείς κάποιο από τα Visual Studio Extensions for Sharepoint ή το STSDEV. Τo custom field template του VSeWSS είναι εντελώς άδειο ενώ το STSDev έχει μείνει πίσω σε features. Σου προτείνω να χρησιμοποιήσεις το WSPBuilder Addin για το Visual Studio, καθώς τα templates του περιέχουν και sample properties αλλά και κώδικα για στοιχειώδες error handling και για αντιμετώπιση κάποιων bug στα ... properties.
)
Ξεκινώντας λοιπόν από το sample κώδικα του template ....
Στην ουσία, τα properties που βλέπεις όταν προσθέτεις ένα custom field είναι απλά properties τα οποία όμως αποθηκεύονται με ειδικό τρόπο μέσω του API του Sharepoint. Αν π.χ. έχεις ένα property που λέγεται MyCustomProperty, ο κώδικας του είναι ο εξής:
public class MyField : SPFieldText
{
...
public string MyCustomProperty
{
get { return this.GetCustomProperty("MyCustomProperty") + ""; }
set { this.SetCustomProperty("MyCustomProperty", value); }
}
Μετά, πρέπει να φτιάξεις μία ξεχωριστή κλάση field editor η οποία θα εμφανίσει τα απαραίτητα controls για κάθε property και αποθηκεύσει τις τιμές πίσω στο πεδίο. Ευτυχώς το WSPBuilder Addin φτιάχνει αυτόματα όλες αυτές τις κλάσεις. H υλοποίηση του Field Editor είναι :
public class MyFieldFieldEditor : UserControl, IFieldEditor
{
// Fields
protected DropDownList DdlLookupFieldTargetList;
private MyField fldMyField;
protected Label LabelLookupFieldTargetListTitle;
public void InitializeWithField(SPField field)
{
this.fldMyField = field as MyField;
if (this.Page.IsPostBack)
{
return;
}
DdlLookupFieldTargetList.Items.Clear();
DdlLookupFieldTargetList.Items.Add("One");
DdlLookupFieldTargetList.Items.Add("Two");
DdlLookupFieldTargetList.Items.Add("Default Value");
DdlLookupFieldTargetList.Items.Add("Four");
if (field != null)
{
DdlLookupFieldTargetList.SelectedValue = fldMyField.MyCustomProperty;
this.DdlLookupFieldTargetList.Visible = true;
}
else
{
DdlLookupFieldTargetList.SelectedValue = "Default Value";
this.DdlLookupFieldTargetList.Visible = true;
}
}
public void OnSaveChange(SPField field, bool bNewField)
{
MyField lookup = (MyField)field;
lookup.IsNew = bNewField;
lookup.MyCustomProperty = this.DdlLookupFieldTargetList.SelectedValue;
}
// Properties
public bool DisplayAsNewSection
{
get
{
return false;
}
}
}
Η υλοποίηση όμως του GetCustomProperty, SetCustomProperty στο SPField έχει bugs, γι αυτό ο κώδικας του WSPBuilder τις κάνει override και κάνει κάποιες διορθώσεις:
private static string[] CustomPropertyNames = new string[] { "MyCustomProperty" };
...
#region Property storage and bug workarounds - do not edit
/// <summary>
/// Indicates that the field is being created rather than edited. This is necessary to
/// work around some bugs in field creation.
/// </summary>
public bool IsNew
{
get { return _IsNew; }
set { _IsNew = value; }
}
private bool _IsNew = false;
/// <summary>
/// Backing fields for custom properties. Using a dictionary to make it easier to abstract
/// details of working around SharePoint bugs.
/// </summary>
private Dictionary<string, string> CustomProperties = new Dictionary<string, string>();
/// <summary>
/// Static store to transfer custom properties between instances. This is needed to allow
/// correct saving of custom properties when a field is created - the custom property
/// implementation is not used by any out of box SharePoint features so is really buggy.
/// </summary>
private static Dictionary<string, string> CustomPropertiesForNewFields = new Dictionary<string, string>();
/// <summary>
/// Initialise backing fields from base property store
/// </summary>
private void InitProperties()
{
foreach (string propertyName in CustomPropertyNames)
{
CustomProperties[propertyName] = base.GetCustomProperty(propertyName) + "";
}
}
/// <summary>
/// Take properties from either the backing fields or the static store and
/// put them in the base property store
/// </summary>
private void SaveProperties()
{
foreach (string propertyName in CustomPropertyNames)
{
base.SetCustomProperty(propertyName, GetCustomProperty(propertyName));
}
}
/// <summary>
/// Get an identifier for the field being added/edited that will be unique even if
/// another user is editing a property of the same name.
/// </summary>
/// <param name="propertyName"></param>
/// <returns></returns>
private string GetCacheKey(string propertyName)
{
return SPContext.Current.GetHashCode() + "_" + (ParentList == null ? "SITE" : ParentList.ID.ToString()) + "_" + propertyName;
}
/// <summary>
/// Replace the buggy base implementation of SetCustomProperty
/// </summary>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
new public void SetCustomProperty(string propertyName, object propertyValue)
{
if (IsNew)
{
// field is being added - need to put property in cache
CustomPropertiesForNewFields[GetCacheKey(propertyName)] = propertyValue + "";
}
CustomProperties[propertyName] = propertyValue + "";
}
/// <summary>
/// Replace the buggy base implementation of GetCustomProperty
/// </summary>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
new public object GetCustomProperty(string propertyName)
{
if (!IsNew && CustomPropertiesForNewFields.ContainsKey(GetCacheKey(propertyName)))
{
string s = CustomPropertiesForNewFields[GetCacheKey(propertyName)];
CustomPropertiesForNewFields.Remove(GetCacheKey(propertyName));
CustomProperties[propertyName] = s;
return s;
}
else
{
return CustomProperties[propertyName];
}
}
/// <summary>
/// Called when a field is created. Without this, update is not called and custom properties
/// are not saved.
/// </summary>
/// <param name="op"></param>
public override void OnAdded(SPAddFieldOptions op)
{
base.OnAdded(op);
Update();
}
#endregion
...
public override void Update()
{
SaveProperties();
base.Update();
}
Φυσικά, τα πράγματα είναι πολύ απλούστερα αν χρησιμοποιήσεις το ... Addin!
Τέλος, θα σου προτείνω να διαβάσεις οπωσδήποτε το άρθρο Custom Field Types του Ted Pattison στο MSDN Magazine, για δύο λόγους:
- Είναι πολύ καλό άρθρο
- Η Microsoft αφαιρεί την CAML από το rendering των custom fields στην επόμενη έκδοση του Sharepoint (καλά ξεκουμπίδια). Ο Pattison δείχνει πως να φτιάξεις το control σου χωρίς τη χρήση CAML και έτσι να αποφύγεις προβλήματα upgrade στο μέλλον, αλλά και τους ανόητους περιορισμούς της CAML στο παρόν.
Απλά, μην χρησιμοποιήσεις το STSDev που προτείνει. Απ' όλα τα extensions και Addins είναι το φτωχότερο και δυσκολότερο.
Παναγιώτης Καναβός, Freelancer
Twitter: http://www.twitter.com/pkanavos