Today's topic might really help you in your SharePoint life. I will present to you a technique to work with SPD workflow and page provisioning to make them portable from one environment to another. Not delaying any further - I will show you real world examples with it's issue:
Example #1. I have created SPD (SharePoint Designer ) workflow and want to export it and import to the another site created from the same site template (different enviroment: Dev, Qc, Staging).
Issue: The exported wf holds hard-coded list id value
Example #2. I have created a XSLT webpart and put it in a module <file> element. This feature should work on every site created from the site template to which this feature is belong to.
Issue: The xslt webpart has a hard-coded ListId.
Ok, I think it's convincing enough to read on to find a solution for described situations.
Here is our custom approach:
We created a feature which we call SiteProvisioning. It consists of 2 elements:
1. A custom class: SiteProvisioning.cs which set as ReceiverClass for the feature:
<Feature Id="{4FA2AC90-321A-4d4c-B587-CE6D4A5B90FA}"
Title="SiteProvisioning"
Description=""
Version="1.0.0.0"
Scope="Web"
Hidden="FALSE"
DefaultResourceFile="core"
ReceiverAssembly="Custom.SharePoint.Case, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a4607a0133bbaeb2"
ReceiverClass="Custom.SharePoint.Case.SiteDefinition.SiteProvisioning"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ActivationDependencies>
<ActivationDependency FeatureId="43DA1A6A-0759-4093-B84D-9593555BC037"/>
</ActivationDependencies> ( I will explain this element later)
<ElementManifests>
<ElementFile Location="SiteProvisioning.xml" />
</ElementManifests>
</Feature>
2. And a file:
<ElementManifests>
<ElementFile Location="SiteProvisioning.xml" />
</ElementManifests>
SiteProvisioning file is a list of elements which are originally have a hard-coded value and related list title;
<SiteSettings>
<FixupFiles>
<FixupFile DataViewInZone="true" DeleteWebPartsOnDeactivation="true" RelativePath="Lists/Discussion/DiscussionView.aspx" />
<FixupFile DataViewInZone="true" DeleteOnDeactivation="true" RelativePath="default.aspx" />
<FixupFile DataViewOutZone="true" DeleteOnDeactivation="true" RelativePath="default.aspx" />
</FixupFiles>
<ListInstances>
<ListInstance Id="fd482aa7-9511-4b0f-abeb-79030bb681ca" Title="Case" />
<ListInstance Id="85ac067e-e536-46f3-9a7b-032016cd032d" Title="Case Review Meeting" />
<ListInstance Id="cde441a0-b34f-4f19-ba47-52b0b5c8d49e" Title="Action Items" />
<ListInstance Id="dd764ce9-aa7a-41aa-aa2f-28841aee3356" Title="Claims" />
<ListInstance Id="9b811048-cbd5-4e41-b526-703f1ddc3fae" Title="Disciplinary Actions" />
<ListInstance Id="A8949622-30DB-4CE6-A6A6-B674DED51355" Title="Involved People" Web="/apps/systems/cases/" />
<ListInstance Id="cbe513fb-ef61-45aa-9162-23398b8a4011" Title="Reports" />
<ListInstance Id="4DF356F2-90CF-4D76-9B38-1B17F3B3D6E9" Title="Tasks" />
</ListInstances>
</SiteSettings>
Every time we put a xslt web part in the module file, we need to be sure that we found a hard-coded value in it and added it to the SiteProvisioning also we need to put a file where we should replace this id during siteprovisioning.
The guy who will be in charge of the replacement is a SiteProvisioning class
Here is an essence of it:
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
if (properties == null)
{
return;
}
SPWeb web = properties.Feature.Parent as SPWeb;
string filePath = this.GetProvisioinerFilePath(properties.Definition);
var sz = new XmlSerializer(typeof(SiteSettings));
SiteSettings settings = null;
if (File.Exists(filePath)) {
using (var sr = File.OpenRead(filePath)) {
try {
settings = sz.Deserialize(sr) as SiteSettings;
} catch { }
}
}
if (settings != null) {
this.RestoreDataViewInZone(web, settings);
this.RestoreDataViewOutZone(web, settings);
}
this.OnActivated(properties);
}
private void RestoreDataViewInZone(SPWeb web, SiteSettings settings)
{
if (settings.FixupFiles == null)
return;
foreach (var xFixupFile in settings.FixupFiles.Where(i => i.DataViewInZone))
{
var relativePath = xFixupFile.RelativePath;
if (string.IsNullOrEmpty(relativePath))
{
continue;
}
SPFile file = web.GetFile(relativePath);
if (file == null)
{
continue;
}
SPLimitedWebPartManager manager = file.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);
SPLimitedWebPartCollection pageWebParts = manager.WebParts;
if (pageWebParts == null)
{
continue;
}
foreach (System.Web.UI.WebControls.WebParts.WebPart webPart in pageWebParts)
{
if (((webPart is DataFormWebPart) && (((DataFormWebPart)webPart).ParameterBindings != null)))
{
this.SubstituteGuidInZone(web, manager, webPart as DataFormWebPart, settings);
this.SubstituteIDInZone(web, manager, webPart, settings);
}
else
{
this.SubstituteIDInZone(web, manager, webPart, settings);
}
}
// http://msdn.microsoft.com/en-us/library/aa973248.aspx
//
// Microsoft.SharePoint.WebPartPages.SPLimitedWebPartManager
// The SPLimitedWebPartManager class contains a reference to an internal SPWeb object that must be disposed.
manager.Web.Dispose();
}
}
private void SubstituteGuidInZone(SPWeb web, SPLimitedWebPartManager manager, DataFormWebPart dataForm, SiteSettings settings)
{
if (settings.ListInstances == null)
{
return;
}
foreach (var xListInstance in settings.ListInstances)
{
if (xListInstance.Id == null || xListInstance.Title == null) {
return;
}
SPList list = null;
try
{
if (string.IsNullOrEmpty(xListInstance.Web))
{
list = web.Lists[xListInstance.Title];
}
else
{
using (SPWeb listWeb = web.Site.OpenWeb(xListInstance.Web))
{
list = listWeb.Lists[xListInstance.Title];
}
}
}
catch (ArgumentException)
{
continue;
}
if (list == null)
{
continue;
}
string newId = list.ID.ToString();
dataForm.ListName = newId;
dataForm.ParameterBindings = Regex.Replace(dataForm.ParameterBindings, xListInstance.Id, newId, RegexOptions.IgnoreCase);
dataForm.DataSourcesString = Regex.Replace(dataForm.DataSourcesString, xListInstance.Id, newId, RegexOptions.IgnoreCase);
dataForm.Xsl = Regex.Replace(dataForm.Xsl, xListInstance.Id, newId, RegexOptions.IgnoreCase);
manager.SaveChanges(dataForm);
}
}
private void SubstituteGuidInZone(SPWeb web, SPLimitedWebPartManager manager, DataFormWebPart dataForm, SiteSettings settings)
{
if (settings.ListInstances == null)
{
return;
}
foreach (var xListInstance in settings.ListInstances)
{
if (xListInstance.Id == null || xListInstance.Title == null) {
return;
}
SPList list = null;
try
{
if (string.IsNullOrEmpty(xListInstance.Web))
{
list = web.Lists[xListInstance.Title];
}
else
{
using (SPWeb listWeb = web.Site.OpenWeb(xListInstance.Web))
{
list = listWeb.Lists[xListInstance.Title];
}
}
}
catch (ArgumentException)
{
continue;
}
if (list == null)
{
continue;
}
string newId = list.ID.ToString();
dataForm.ListName = newId;
dataForm.ParameterBindings = Regex.Replace(dataForm.ParameterBindings, xListInstance.Id, newId, RegexOptions.IgnoreCase);
dataForm.DataSourcesString = Regex.Replace(dataForm.DataSourcesString, xListInstance.Id, newId, RegexOptions.IgnoreCase);
dataForm.Xsl = Regex.Replace(dataForm.Xsl, xListInstance.Id, newId, RegexOptions.IgnoreCase);
manager.SaveChanges(dataForm);
}
}
Whew, you have made it almost to the end. I hope that you have grasped the concept of id replacement: Regex.Replace(dataForm.ParameterBindings, xListInstance.Id, newId, RegexOptions.IgnoreCase); understand how to keep the hardcoded value for replacement.
The procedure pretty much the same for SPD WF. Once you have figured out where SPD hard codes the value - you write the class to replace id with current id of the list on the current site.
The only technique I haven't covered yet - how and when apply the site provisioning for the hardcoded webpart on the page.
Here is a neat approach - use <ActivationDependencies> element in the site provisioning feature to call the module feature which holds hard-coded web parts.
Let's summarize:
1. We have a module feature with hardcoded XSLT web parts.
2. We have a site provisioning feature which has activation dependency to the module feature.
It means every time the site provisioning gets activated, the module gets activated first. In such way we handle a fresh new pages and web parts on the site by SiteProvisioning.
3. We have a settings file for site provisioning which holds the hard coded ids and the related list name.
4. We have a custom class to perform the id replacement.
That's it! We are done! We are enlightened!
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
if (properties == null)
{
return;
}
SPWeb web = properties.Feature.Parent as SPWeb;
string filePath = this.GetProvisioinerFilePath(properties.Definition);
var sz = new XmlSerializer(typeof(SiteSettings));
SiteSettings settings = null;
if (File.Exists(filePath)) {
using (var sr = File.OpenRead(filePath)) {
try {
settings = sz.Deserialize(sr) as SiteSettings;
} catch { }
}
}
if (settings != null) {
this.RestoreDataViewInZone(web, settings);
this.RestoreDataViewOutZone(web, settings);
}
this.OnActivated(properties);
}
private void RestoreDataViewInZone(SPWeb web, SiteSettings settings)
{
if (settings.FixupFiles == null)
return;
foreach (var xFixupFile in settings.FixupFiles.Where(i => i.DataViewInZone))
{
var relativePath = xFixupFile.RelativePath;
if (string.IsNullOrEmpty(relativePath))
{
continue;
}
SPFile file = web.GetFile(relativePath);
if (file == null)
{
continue;
}
SPLimitedWebPartManager manager = file.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);
SPLimitedWebPartCollection pageWebParts = manager.WebParts;
if (pageWebParts == null)
{
continue;
}
foreach (System.Web.UI.WebControls.WebParts.WebPart webPart in pageWebParts)
{
if (((webPart is DataFormWebPart) && (((DataFormWebPart)webPart).ParameterBindings != null)))
{
this.SubstituteGuidInZone(web, manager, webPart as DataFormWebPart, settings);
this.SubstituteIDInZone(web, manager, webPart, settings);
}
else
{
this.SubstituteIDInZone(web, manager, webPart, settings);
}
}
// http://msdn.microsoft.com/en-us/library/aa973248.aspx
//
// Microsoft.SharePoint.WebPartPages.SPLimitedWebPartManager
// The SPLimitedWebPartManager class contains a reference to an internal SPWeb object that must be disposed.
manager.Web.Dispose();
}
}
private void SubstituteGuidInZone(SPWeb web, SPLimitedWebPartManager manager, DataFormWebPart dataForm, SiteSettings settings)
{
if (settings.ListInstances == null)
{
return;
}
foreach (var xListInstance in settings.ListInstances)
{
if (xListInstance.Id == null || xListInstance.Title == null) {
return;
}
SPList list = null;
try
{
if (string.IsNullOrEmpty(xListInstance.Web))
{
list = web.Lists[xListInstance.Title];
}
else
{
using (SPWeb listWeb = web.Site.OpenWeb(xListInstance.Web))
{
list = listWeb.Lists[xListInstance.Title];
}
}
}
catch (ArgumentException)
{
continue;
}
if (list == null)
{
continue;
}
string newId = list.ID.ToString();
dataForm.ListName = newId;
dataForm.ParameterBindings = Regex.Replace(dataForm.ParameterBindings, xListInstance.Id, newId, RegexOptions.IgnoreCase);
dataForm.DataSourcesString = Regex.Replace(dataForm.DataSourcesString, xListInstance.Id, newId, RegexOptions.IgnoreCase);
dataForm.Xsl = Regex.Replace(dataForm.Xsl, xListInstance.Id, newId, RegexOptions.IgnoreCase);
manager.SaveChanges(dataForm);
}
}
private void SubstituteGuidInZone(SPWeb web, SPLimitedWebPartManager manager, DataFormWebPart dataForm, SiteSettings settings)
{
if (settings.ListInstances == null)
{
return;
}
foreach (var xListInstance in settings.ListInstances)
{
if (xListInstance.Id == null || xListInstance.Title == null) {
return;
}
SPList list = null;
try
{
if (string.IsNullOrEmpty(xListInstance.Web))
{
list = web.Lists[xListInstance.Title];
}
else
{
using (SPWeb listWeb = web.Site.OpenWeb(xListInstance.Web))
{
list = listWeb.Lists[xListInstance.Title];
}
}
}
catch (ArgumentException)
{
continue;
}
if (list == null)
{
continue;
}
string newId = list.ID.ToString();
dataForm.ListName = newId;
dataForm.ParameterBindings = Regex.Replace(dataForm.ParameterBindings, xListInstance.Id, newId, RegexOptions.IgnoreCase);
dataForm.DataSourcesString = Regex.Replace(dataForm.DataSourcesString, xListInstance.Id, newId, RegexOptions.IgnoreCase);
dataForm.Xsl = Regex.Replace(dataForm.Xsl, xListInstance.Id, newId, RegexOptions.IgnoreCase);
manager.SaveChanges(dataForm);
}
}
Whew, you have made it almost to the end. I hope that you have grasped the concept of id replacement: Regex.Replace(dataForm.ParameterBindings, xListInstance.Id, newId, RegexOptions.IgnoreCase); understand how to keep the hardcoded value for replacement.
The procedure pretty much the same for SPD WF. Once you have figured out where SPD hard codes the value - you write the class to replace id with current id of the list on the current site.
The only technique I haven't covered yet - how and when apply the site provisioning for the hardcoded webpart on the page.
Here is a neat approach - use <ActivationDependencies> element in the site provisioning feature to call the module feature which holds hard-coded web parts.
Let's summarize:
1. We have a module feature with hardcoded XSLT web parts.
2. We have a site provisioning feature which has activation dependency to the module feature.
It means every time the site provisioning gets activated, the module gets activated first. In such way we handle a fresh new pages and web parts on the site by SiteProvisioning.
3. We have a settings file for site provisioning which holds the hard coded ids and the related list name.
4. We have a custom class to perform the id replacement.
That's it! We are done! We are enlightened!
No comments:
Post a Comment