Deploy multiple Office Solutions in a single setup

How to deploy multiple Office Solutions in a single setup

After creation of all your MS-Office solutions, they can be configured to be deployed all at once. The steps below walk through how to configure one setup for all MS-Office solutions using a Click Once installer for the .NET Framework:

Step 1: Create a sample Word Add-In project using VS

(File->New Project->Office/SharePoint->Office Add-ins)

Step 2: Add Projects to the Solution

Add all the projects into this solution where the sample add-in is created.

Step 3: Publish project one at a time to the common folder

(For Ex. ‘C:/Publish/’). Publish the sample add-in project last, when you have finished publishing the other projects . (Note: Ensure that publish settings are set correctly. Add the necessary prerequisites to each project, select install prerequisites as the same location as my application. By selecting this one must ensure that all prerequisites are at this location ‘C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\Bootstrapper\Packages’) in the appropriate folder.

Screen-Shot: Publishing to common place folder:
Multiple Office AddIn1

Screen-Shot: Here we have taken ‘SetupForAddin’ folder (as the common location) to publish all add-in projects:
Multiple Office AddIn2

Step 4: Modifying the application manifest

  1. From the c:\publish\Application Files folder, move the contents of the {EXCEL_PROJECT}_1_0_0_0 and {WORD_PROJECT}_1_0_0_0 or {PPT_PROJECT}_1_0_0_0 directories to the {SAMPLE_PROJECT}_1_0_0_0 directory.
  2. Open the {SAMPLE_PROJECT}.dll.manifest, {WORD_PROJECT}.dll.manifest, and {EXCEL_PROJECT}.dll.manifest, {PPT_PROJECT}.dll.manifest files in an XML editor.
  3. From the {WORD_PROJECT}.dll.manifest file, copy all install and file dependencies including {WORD_PROJECT}.dll. That is, copy all dependencies that start with
  4. In the {SAMPLE_PROJECT}.dll.manifest file, paste the {WORD_PROJECT}.dll install dependency at the end of the dependency section.
  5. Repeat the previous #3 and #4 for {EXCEL_PROJECT} and {PPT_PROJECT}.

Screen-shot: move files from the add-in projects to our last published sampled project directory. (Files to move from each project are ‘.vsto’, ‘dll.deploy’,’dll.manifest’):

Multiple Office AddIn3

 

Screen-Shot: Copied all the element from all word, excel, PowerPoint add-in projects to sample projects {SAMPLE_PROJECT}.dll.manifest file:

Multiple Office AddIn4

Step 5: Adding the Entrypoints

  1. In the {SAMPLE_PROJECT}.dll.manifest file, remove the text between the and elements.
  2. From the {WORD_PROJECT}.dll.manifest file, copy the text between the and elements.
  3. In the {SAMPLE_PROJECT}.dll.manifest file, paste the code after the element.
  4. Add the id attribute to the element to differentiate this entrypoint from others.
  5. Repeat previous points #2, #3 and #4 for {EXCEL_PROJECT} and {PPT_PROJECT}.

Screen-Shot: Copied all the element from all word, excel, PowerPoint add-in projects to sample projects {SAMPLE_PROJECT}.dll.manifest file.

Multiple Office AddIn5

Step 6: Adding Assemblies

To add the assemblies to the vstov4 namespace in the application manifest

  1. In the {SAMPLE_PROJECT}.dll.manifest file, remove any text between the and elements.
  2. In the {WORD_PROJECT}.dll.manifest file, copy the text between the and elements.
  3. In the {SAMPLE_PROJECT}.dll.manifest file, paste the code after the element.
  4. Add the id attribute to the element to differentiate this customization from others. This id is the same id that was added to the element in the previous step 5.

Screen-Shot: Copied all the element from all word, excel, PowerPoint add-in projects to sample projects {SAMPLE_PROJECT}.dll.manifest file.

Multiple Office AddIn6

Step 7: Sign manifests

To re-sign the application and deployment manifests

  1. Copy the {SAMPLE_PROJECT}_TemporaryKey.pfx certificate file from the {SAMPLE_PROJECT} solution directory into the c:\Publish\Application Files\{SAMPLE_PROJECT}_1_0_0_0directory.
  2. Open the Visual Studio command prompt.
  3. Change to the c:\Publish\Application Files\{SAMPLE_PROJECT}_1_0_0_0 directory.
  4. Sign the modified application manifest with the following command:
    mage -sign ContosoInstaller.dll.manifest -certfile ContosoInstaller_TemporaryKey.pfx

The message “{SAMPLE_PROJECT}.dll.manifest successfully signed” appears.

  1. Change to the c:\Publish directory.
  2. Update and sign the deployment manifest with the following command:

mage -update ContosoInstaller.vsto -appmanifest “Application Files\ContosoInstaller_1_0_0_0\ContosoInstaller.dll.manifest” -certfile “Application Files\ContosoInstaller_1_0_0_0\ContosoInstaller_TemporaryKey.pfx”
The message “{SAMPLE_PROJECT}.vsto successfully signed” appears.

Copy the {SAMPLE_PROJECT}.vsto file to the c:\publish\Application Files\{SAMPLE_PROJECT}_1_0_0_0 directory.

Commands to execute: (Use ‘Developer Command Prompt for VS2013’ command prompt from Visual Studio Tools)

  1. mage -sign XYZOfficeAddIn.OfficeAddInSetup.dll.manifest -Publisher “XYZ Products” -Password xyz@123 -certfile XYZOfficeAddIn.OfficeAddInSetup_TemporaryKey.pfx
  2. mage -update XYZOfficeAddIn.OfficeAddInSetup.vsto -Publisher “XYZ Products” -Password xyz@123 -appmanifest “Application Files\ XYZOfficeAddIn.OfficeAddInSetup_1_0_0_3\ XYZOfficeAddIn.OfficeAddInSetup.dll.manifest” -certfile “Application Files\ XYZOfficeAddIn.OfficeAddInSetup_1_0_0_3\ XYZOfficeAddIn.OfficeAddInSetup_TemporaryKey.pfx”

Screen-Shot: Run the ‘Developer Command Prompt for VS2013’ command prompt from VS tools:

Multiple Office AddIn7

Screen-Shot: Re-sign the application and deployment manifests:

Multiple Office AddIn8

Now your final setup is ready. You can run to install. You should then see the Add-Ins configured; for Word, Excel and PowerPoint

Enhancing SharePoint breadcrumbs when navigating deeply nested folders

I had an interesting challenge to improve breadcrumbs in navigating a deeply folder nested library. The nice breadcrumb dropdown icon is actually gone in SP2013 by default; that can easily be re-enabled via CSS. an onMouseOver event action in JavaScript can eliminate the need to click on it. However to improve the breadcrumb navigation, I created an SPWeb feature to enhance breadcrumbs by overriding the OnPreRender event. The OnPreRender should only occur if the ‘Visible’ property of the web control is set to true. The PreRender event is raised just before the page is about to render its contents. This is the last chance we have to modify the page output before it is received by the browser.

I deployed this web Scoped solution that overrides the breadcrumbs with OnPreRender page processing, and provides the full folder path, clickable for each link. It recursively searches the page for the “TitleBreadcrumb” control, and builds a replacement, adding an a href, folder link name, and breadcrumb image iteratively. It only affects the page when you are navigating within a library.

namespace MyDocLibraryBreadcrumb
{
public class MyDocLibBreadcrumbDelegateControl : WebControl 
{
protected override void OnPreRender(EventArgs e)
{
try
{
base.OnPreRender(e);
// Get the path to the current folder
string path = Context.Request.QueryString["RootFolder"];
// if there's no path then there is nothing to do; it implies are are not in the context of the library 
if (String.IsNullOrEmpty(path))
{
return;
}
// Let's get the current folder
SPWeb web = SPContext.Current.Web;
SPFolder currentFolder = web.GetFolder(path);
// Let's find the breadcrumb control on the current page - it's a ListProperty control where the property is  "TitleBreadcrumb". 
Control c = Utils.FindRecursive(Page.Controls, ctrl => ctrl is ListProperty && ((ListProperty)ctrl).Property == "TitleBreadcrumb");
// If not found, nothing to do, and we are not likely in a library. 
if (c == null)
return;
// Let's subsitute the OOTB breadcrumb control with our replacement enhanced one
var parent = c.Parent;
var index = parent.Controls.IndexOf(c);
parent.Controls.RemoveAt(index);
parent.Controls.AddAt(index, new LiteralControl { Text = GetReplacementBreadCrumbOutput(currentFolder) });
}
catch (Exception ex)
{
// log errors quietly 
Utils.WriteMyLog(Utils.GetErrorInfo(ex));
}
}
/// SPFolder is the parameter to create navigation to
/// returns the HTML output
private string GetReplacementBreadCrumbOutput(SPFolder folder)
{
List<BreadcrumbNodeData> nodes = new List<BreadcrumbNodeData>();
// Collect a path from current folder to its root folder
SPFolder nodeFolder = folder;
while (nodeFolder != null)
{
// If we're in folder use the folder name as a title. If not use a library title enstead.
BreadcrumbNodeData node  = new BreadcrumbNodeData();
node.Url = nodeFolder.ServerRelativeUrl;
if (string.IsNullOrEmpty(nodeFolder.ParentFolder.Url))
{
if (nodeFolder.DocumentLibrary != null)
nodes.Add(new BreadcrumbNodeData
{Title = nodeFolder.DocumentLibrary.Title, Url = nodeFolder.ServerRelativeUrl});
}
else
{
nodes.Add(new BreadcrumbNodeData { Title = nodeFolder.Name, Url = nodeFolder.ServerRelativeUrl });
}
nodeFolder = string.IsNullOrEmpty(nodeFolder.ParentFolder.Url) ? null : nodeFolder.ParentFolder;
}
// Reverse the collected path because the root folder must be on the left in bredcrumb
nodes.Reverse();
// Create an HTML output similar to original. An arrow image we've created from the original
string htmlOutput = String.Empty;
foreach (var node in nodes)
{
if (node != nodes.Last())
htmlOutput +=
String.Format(
@"<A href=""{0}"">{1}</A> <IMG style=""vertical-align:middle"" alt=: src=""/_layouts/images/MyDocLibraryBreadcrumb/breadcrumb_arrow.png""/> ", node.Url, node.Title);
else
{
htmlOutput += node.Title;
}
}
return htmlOutput;
}
}
/// temporary class to holds navigation node data
public class BreadcrumbNodeData
{
/// Title for URL (it will be a folder name)
public string Title { get; set; }
/// Url to navigate on click (it will be a server relative URL of the folder)
public string Url { get; set; }
}
public class Utils
{
public static string GetErrorInfo(Exception ex)
{
string result = "ex.Message=" + ex.Message;
result += ex.InnerException == null ? "|ex.StackTrace=" + ex.StackTrace : String.Empty;
if (ex.InnerException != null)
result += "[INNER EXCEPTION: ]" + GetErrorInfo(ex.InnerException);
return result;
}
public static void WriteMyLog(string text)
{
SPDiagnosticsService.Local.WriteTrace(0,
new SPDiagnosticsCategory("MyDocLibBreadcrumbDelegateControl", TraceSeverity.High,
EventSeverity.Error),
TraceSeverity.High, text, null);
}
/// Finds a control based on provided criteria inside controls hierarchy
/// <param name="controls">A Controls collection to start search</param>
/// <param name="criteria">A criteria to return control</param>
/// <returns>The founded control </returns>
public static Control FindRecursive(ControlCollection controls, Func<Control, bool> criteria)
{
foreach (Control control in controls)
{
if (criteria(control))
return control;
var innerControl = FindRecursive(control.Controls, criteria);
if (innerControl != null)
return innerControl;
}
return null;
}
}
}

The SharePoint internals behind an Append text field

SharePoint internals of the Append text field

In configuring a SharePoint field, there’s an option to “Append Changes to Existing Text” for Multi-line text fields. This setting requires versioning be enabled. Let’s delve into why versioning must be enabled for this feature to be enabled.

In a nutshell, SharePoint puts the latest value within the property bag, which you can see if you grab the SPItem object, and dump the read-only SPItem.xml. First let’s examine in PowerShell.

# let's get the web:
$web=Get-SPWeb http ://SharePoint/sites/Site/web
# let's get the list:
$list = $web.Lists["Name of List"]
# let's get the SPItem:
$q=$list.GetItemById(4567)
#Let's set the internal field name for later reference:
$FName = "Internal Field Name"

A more elegant way is by addressing by URL directly:

$url="http ://SharePoint/sites/Site/web/Listname/4567_.000"
$item = $web.GetListItem($url)

Then we can peek into the property bag:

$item.xml

It is worth noting there are times when SharePoint does not put the latest value in the property bag. In fact the property is absent altogether, although the versions continue to capture the append text. One theory is that this occurs to SP2007 lists that have been upgraded. If you experience this behavior, read below for accessing all of the append text.

But where are the previous appends? How can we see them? The trick is to walk through the versions, grabbing the version SPItem, and then grab the field value.

foreach ($v in $item.versions)
{
$v.get_Item($FName)
}

In C# we extract in a function such as this:

public static string GetVersionedMultiLineTextAsPlainText(int ID, string field,SPList list)
{
SPListItem item = list.GetItemById(ID);
StringBuilder sb = new StringBuilder();
foreach (SPListItemVersion version in item.Web.Lists[item.ParentList.ID].Items[item.UniqueId].Versions)
{
SPFieldMultiLineText CommentsField = version.Fields.GetFieldByInternalName(field) as SPFieldMultiLineText;
if (CommentsField != null)
{
string comment = CommentsField.GetFieldValueAsText(version[field]);
if (comment != null && comment.Trim() != string.Empty)
{
sb.Append("");
sb.Append(version.CreatedBy.User.Name).Append(" (");
sb.Append(version.Created.ToString("MM/dd/yyyy hh:mm tt"));
sb.Append(") ");
sb.Append(comment);
}
}
}
return sb.ToString();
}

Extending the Outlook Ribbon programmatically

Extending the Outlook Ribbon

The MS-Office ribbon can be extended programmatically. In this article, I add a new tab to the ribbon, and new icons that open links to sites, then create a ClickOnce deployable package. We’ll explore a few tricks including how to import new icons. For a great overview of the Fluent UI, please see: https://msdn.microsoft.com/en-us/library/aa338198.aspx

Ribbon XML

Microsoft designed the Fluent UI (aka Ribbon) to be extensible. The easiest way to do this is via the Ribbon Designer. However I chose to hand-craft the solution based on Ribbon XML. After creating a new Visual Studio 2010 project, ensuring use of .NET 4.0,

To use an existing MS-Office Tab, just reference it by name, using tab idMso:

<customUI
xmlns="http://schemas.microsoft.com/office/2009/07/customui">
<ribbon startFromScratch="false">
<!-- Ribbon XML -->
</ribbon>
<backstage>
<button idMso="FileSave" visible="false"/>
<button idMso="SaveObjectAs" visible="false"/>
<button idMso="FileSaveAsCurrentFileFormat" visible="false"/>
<button idMso="FileOpen" visible="false"/>
<button idMso="FileCloseDatabase" visible="false"/>
<tab idMso ="TabInfo" visible="false"/>
<tab idMso ="TabRecent" visible="false"/>
<tab idMso ="TabNew" visible="false"/>
<tab idMso ="TabPrint" visible="false"/>
<tab idMso ="TabShare" visible="false"/>
<tab idMso ="TabHelp" visible="false"/>
<button idMso="ApplicationOptionsDialog" visible="false"/>
<button idMso="FileExit" visible="false"/>
</backstage>
</customUI>

However you can create your own tab. Just remember to give it a unique ID, and also add the label. Note the use of “id” rather than “idMso”:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customUI onLoad="RibbonOnLoad" xmlns="http://schemas.microsoft.com/office/2006/01/customui">
<commands>
<command idMso="ApplicationOptionsDialog" enabled="false"/>
<command idMso="FileExit" enabled="false"/>
</commands>
<ribbon startFromScratch="true">
<officeMenu>
<button idMso="FileNew" visible="false"/>
<button idMso="FileOpen" visible="false"/>
<button idMso="FileSave" visible="false" />
</officeMenu>
<tabs>
<tab id="MyCustomHomeTab" label="Home" insertAfterMso="TabHome" getVisible="GetVisible" tag="ribhome">
<group idMso="GroupClipboard" />
<group idMso="GroupFont" />
<group idMso="GroupAlignmentExcel" />
<group idMso="GroupNumber" />
<group idMso="GroupStyles" />
<group idMso="GroupCells" />
<group idMso="GroupEditingExcel" />
</tab>
<tab id="MyCustomInsertTab" label="Insert" insertAfterMso="TabInsert" getVisible="GetVisible" tag="ribinsert" >
<group idMso="GroupInsertTablesExcel" />
<group idMso="GroupInsertIllustrations" />
<group idMso="GroupInsertChartsExcel" />
<group idMso="GroupInsertLinks" />
<group idMso="GroupInsertText" />
</tab>
<tab id="MyCustomPageLayoutTab" label="Page Layout" insertAfterMso="TabPageLayoutExcel" getVisible="GetVisible" tag="ribpagelayout">
<group idMso="GroupThemesExcel" />
<group idMso="GroupPageSetup" />
<group idMso="GroupPageLayoutScaleToFit" />
<group idMso="GroupPageLayoutSheetOptions" />
<group idMso="GroupArrange" />
</tab>
<tab id="MyCustomFormulasTab" label="Formulas" insertAfterMso="TabFormulas" getVisible="GetVisible" tag="ribformulas">
<group idMso="GroupFunctionLibrary" />
<group idMso="GroupNamedCells" />
<group idMso="GroupFormulaAuditing" />
<group idMso="GroupCalculation" />
</tab>
<tab id="MyCustomDataTab" label="Data" insertAfterMso="TabData" getVisible="GetVisible" tag="ribdata">
<group idMso="GroupGetExternalData" />
<group idMso="GroupConnections" />
<group idMso="GroupSortFilter" />
<group idMso="GroupDataTools" />
<group idMso="GroupOutline" />
</tab>
<tab id="MyCustomReviewTab" label="Review" insertAfterMso="TabReview" getVisible="GetVisible" tag="ribreview">
<group idMso="GroupProofing" />
<group idMso="GroupComments" />
<group idMso="GroupChangesExcel" />
</tab>
<tab id="MyCustomViewTab" label="View" insertAfterMso="TabView" getVisible="GetVisible" tag="ribview">
<group idMso="GroupWorkbookViews" />
<group idMso="GroupViewShowHide" />
<group idMso="GroupZoom" />
<group idMso="GroupWindow" />
<group idMso="GroupMacros" />
</tab>
<tab id="MyCustomDeveloperTab" label="Developer" insertAfterMso="TabDeveloper" getVisible="GetVisible" tag="ribdeveloper">
<group idMso="GroupCode" />
<group idMso="GroupControls" />
<group idMso="GroupXml" />
<group idMso="GroupModify" />
</tab>
</tabs>
</ribbon>
</customUI>

For your image, you can reference existing MS-Office images, which is easiest and faimilar to users. Note that tag “imageMso” is used. The other advantage, as we’ll soon see, is these images are already sized, and loaded correctly:

imageMso="RecordsAddFromOutlook"

MS-Office has a great set of familiar icons you can leverage. These are easier to use than importing your own (which we will get to shortly). To select from existing icons, check out this great site, which includes the XML to reference the MS-Office images:
Ribbon images

To load your own images, first import the desired images into the Visual Studio project; under Resources is a great place. I recommend right clicking and marking each to “Include”. These images will only ever appear if you explicitly binary stream them in on Ribbon load. Here’s how. At the start of the MyRibbonxml, add a mew reference method called “GetImage” to load the images you will reference in this MyRibbon.xml:

imageMso="RecordsAddFromOutlook"

Rather than using imageMso, just use an image tag:

image="DMFLogo.ico"

Now for every image you reference, the loadImage method will be called, passing in the name of the image as a parameter. Let’s now add the getImage method to the myRibbon.cs:

public Bitmap GetImage(string imageName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
//this is useful for navigating to discover the actual resource name reference
//string[] x;
//x = assembly.GetManifestResourceNames();
Stream stream = assembly.GetManifestResourceStream("MyProjectName.Resources." + imageName);
return new Bitmap(stream);  //uses system.drawing, added for now
}

At runtime, the stream is loaded based on the manifest reference. During initial debugging, it’s great to be able to see the manifest to ensure the path is correct. I’ve found it best to have a few lines of code to give you a handle on the manifest during debugging, if ever you need, just uncomment these and examine at a breakpoint:

//string[] x;
//x = assembly.GetManifestResourceNames();

As the images are stored in a folder, you’ll have to get the project name reference and the “Resources” folder path correct.

One last point is I found ICO files don’t give the best resolution. Microsoft recommends PNGs, which is a good image format to start.

To open a browser as a ribbon action, adapt this function:

public void ActionOpenInsureds(Office.IRibbonControl control)
{
string targetURL = "http ://sharepoint/sites/MySite/SitePages/Index1.aspx";
System.Diagnostics.Process.Start(targetURL);
return;
}

This function is called based on the button onAction tag in myRibbon.xml:

onAction="ActionOpenInsureds"

For completeness, here’s the full sample myRibbon.xml:

<?xml version="1.0" encoding="UTF-8"?>
<button id="textButton"></button>
<button id="DMFButton"></button>
<button id="tableButton"></button>

ClickOnce

ClickOnce is a fabulous technology. Previously we had to build a deployment project, or purchase 3rd-party software. This ribbon can be published to a central location as a ClickOnce install. It will appear in the Control Panel, and it can check in itself for updates. Here’s a great overview of ClickOnce: https://msdn.microsoft.com/en-us/library/t71a733d

To get started, simply select “Build, Publish” in Visual Studio 2010 and follow the menus.

A few things to note:
1. Unless you sign your code with a digital certificate, users will get prompted
2. The cert you use needs to have “code signing” authority, best is to get one issued by the CA (Certificate Authority) in your organization
3. If you set pre-requisites, the user needs admin authority on their machine in order to check-pre-requisites
4. There’s a registry change to control the .NET install prompts
5. Click-once does not support install for “All users” on the machine, only the current user
6. Even trying to run in silent mode still results in a single-acknowledgement window after the install

Silence is golden

I would expect running off My Documents would end up in the MyComputer Zone, which should be “Enabled” as a trusted zone for install without prompt, but here’s the keys to experiment with:

HKLMSoftwareMicrosoft.NETFrameworkSecurityTrustManagerPromptingLevel.

Then add the following string values for one or more of the security zones, as appropriate:

  • MyComputer
  • LocalIntranet
  • Internet
  • TrustedSites
  • UntrustedSites

Set the value of each security zone to one of the following:Enabled. The installation prompts the user if the solution is not signed with a trusted certificate. This is the default for the MyComputer, LocalIntranet, and TrustedSites zones.AuthenticodeRequired. The user cannot install the solution if it is not signed with a trusted root authority certificate. The installation prompts the user if the solution is not signed with a trusted publisher certificate. This is the default for the Internet zone.Disabled. The user cannot install the solution if it is not signed with a trusted publisher certificate. This is the default for the UntrustedSites zone.

The last option is to add this solution to the inclusion list, which is a list of trusted solutions.

Folder Metadata Web Part

Creating a Folder Metadata Web Part

An interesting challenge I faced was programmatically exposing custom folder metadata within a specific Document Library View, for specific Folder Content Types. As background, I already created Folder Content Types, deriving from the SharePoint Folder Content Type, to which I added appropriate metadata. These new Content Types were syndicated from a Content Type Hub, and propagated across the enterprise.

I extended the ASP template to include a table that I will build dynamically, and a button that I optionally hide:

<asp:Table
ID="Table1" runat="server">
</asp:Table<>asp:Button ID="PlaceholderButton" runat="server" Text="Create New Placeholder" onclick="PlaceholderButton_Click" />

I then add an action for the button:

protected void PlaceholderButton_Click(object sender, EventArgs e) 
{
Response.Redirect("http ://sharepoint/div/myDiv/Lists/MyListTasks/NewForm.aspx?RootFolder="); 
} 

This useful function adds a table row consisting pair of table cells with a property label (in bold) and the property value, but only if the property/value is found, on the current Document. Note Content Type is detected and handled separately:

void push_property(string myLabel, string propertyName)
{
try
{
string val = null;
if (propertyName == "ContentType")
val = folder.Item.ContentType.Name;
else
val = folder.Item.Properties[propertyName].ToString();
if (!String.IsNullOrEmpty(val))
{
TableRow rw = new TableRow();
TableCell cel = new TableCell();
cel.Text = string.Concat("<b>", myLabel, "</b>");
rw.Cells.Add(cel);
cel = new TableCell();
cel.Text = val;
rw.Cells.Add(cel);
Table1.Rows.Add(rw);
}
}
catch { }
}

This is the heart of this little web-part. I derive the actual folder from the user context, by extracting it from the browser URL string using Page.Request.QueryString[“RootFolder”]:

protected void Page_Load(object sender, EventArgs e)
{
bool ok = true;
string ctName = null;
SPWeb web = SPContext.Current.Web;
string rootFolder = Page.Request.QueryString["RootFolder"];
//Label2.Text = rootFolder;
if (String.IsNullOrEmpty(rootFolder))
ok = false;
if (ok)
{
folder = web.GetFolder(rootFolder);
if (!folder.Exists)
ok=false;
}
if (ok)
{
ctName = folder.Item.ContentType.Name;
if ((ctName != "Divison Acct Folder") && (ctName != "Divison Common Folder"))
ok=false;
}
PlaceholderButton.Visible = ok;  //reacts dynamically, needs setting in both directions, as it maintains state
if (ok)
{
//push_property("Folder Type", "ContentType");  //Handled in special fashion internal to function
push_property("Claimant", "Claimant");      
push_property("Account Number", "AccountNumber");
push_property("Issue Description", "IssueDescription");
/*Only apply this border logic if you want a griddy view
if (Table1.Rows.Count >  0)
{
Table1.GridLines = (GridLines) 3;
}
*/
}
}

Writing to ULS from within C#

Writing to ULS from within C#

Event Receivers and Feature Receivers are notoriously hard to debug.  Here’s a little gem to ease your debugging migraines:

Here’s the function I use, just to simplify subsequent calls:

private void LogOut(TraceSeverity Level, string OutStr)
        {
            SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("SuperRouter Updated", Level, EventSeverity.Error), TraceSeverity.Unexpected, OutStr, null);
        }

Here’s  what the call looks like. Note you can set the severity level.  I sprinkle these throughout the code and dump data etc throughout

LogOut(TraceSeverity.High, "Item Updated Event Trapped!");

Here’s the reference to use in the code:

using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Administration;

 

If you’d like a class to reuse, here it is:

public class MyLogger
{
public static void LogOut(TraceSeverity Level, string OutStr)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("JoelER", Level, EventSeverity.Error), TraceSeverity.Unexpected, OutStr, null);
}
}

Using the SP2013 Client Object Model in InfoPath managed code

Someone asked me today about using the SharePoint 2013 Client Object Model in Managed Code.  I’ve done this in VSTA (Visual Studio for Tools and Applications) in C#.  It took a bit of experimentation to get it going, here’s how I did it:

I extracted the Client Object Model runtime DLLs from the SharePoint server.  That’s both Microsoft.SharePoint.Client.dll and Microsoft.SharePoint.Client.runtime.dll.  For good measure I also took Microsoft.SharePoint.Client.Runtime.dll.deploy

In my C# (I was using VSTA) as for Managed C# in InfoPath, I:

  1. Added three project references:
    1. System.Core
    2. SharePoint.Client
    3. SharePoint.Client.Runtime
  2. Added Using for SharePoint.Client

Opened AssemblyInfo.cs to deal with partial trust exception, and added:

using System.Security; //Added to try to resolve the partial trust error: JP
[assembly: AllowPartiallyTrustedCallers]   //Added to try to resolve the partial trust error: JP

I had to turn the form  from “Automatically determine security level” to “Full Trust”.  This is an InfoPath specific setting, that may affect how this runs as a Farm Solution, and whether it needs a Farm Administrators approval.

At that point, you can use the Client Object Model.  Getting data is done two different ways:

–          CAML: my native old-style preferred approach for simple queries

–          LINQ: for SQL-like queries; the one catch is you need to use SPMetal to create a runtime Thunk of sorts specific to your target sites/lists

Hope that helps!