The situation:
I have a bunch of Terms in the Term Store and a list that uses them.
A lot of the terms have not been used yet, and are not available yet in the TaxonomyHiddenList.
If they are not there yet they don't have an ID, and I can not add them to a list item.
There is a method GetWSSIdOfTerm on Microsoft.SharePoint.Taxonomy.TaxonomyField that's supposed to return the ID of a term for a specific site.
This gives back IDs if the term has already been used and is present in the TaxonomyHiddenList, but if it's not then 0 is returned.
Is there any way to programmatically add terms to the TaxonomyHiddenList or force it happening?
Don't use
TaxonomyFieldValue tagValue = new TaxonomyFieldValue(termString);
myItem[tagsFieldName] = tagValue;"
because you will have errors when you want to crawl this item.
For setting value in a taxonomy field, you have just to use :
tagsField.SetFieldValue(myItem , myTerm);
myItem.Update();"
Regards
In case of usage
string termString = String.Concat(myTerm.GetDefaultLabel(1033),
TaxonomyField.TaxonomyGuidLabelDelimiter, myTerm.Id);
then during instantiation TaxonomyFieldValue
TaxonomyFieldValue tagValue = new TaxonomyFieldValue(termString);
exception will be thrown with message
Value does not fall within the expected range
You have additionally provide WssId to construct term string like shown below
// We don't know the WssId so default to -1
string termString = String.Concat("-1;#",myTerm.GetDefaultLabel(1033),
TaxonomyField.TaxonomyGuidLabelDelimiter, myTerm.Id);
On MSDN you can find how to create a Term and add it to TermSet. Sample is provided from TermSetItem class description. TermSet should have a method CreateTerm(name, lcid) inherited from TermSetItem. Therefore you can use it in the sample below int catch statement ie:
catch(...)
{
myTerm = termSet.CreateTerm(myTerm, 1030);
termStore.CommitAll();
}
As for assigning term to list, this code should work (i'm not sure about the name of the field "Tags", however it's easy to find out the proper internal name of the taxonomy field):
using (SPSite site = new SPSite("http://myUrl"))
{
using (SPWeb web = site.OpenWeb())
{
string tagsFieldName = "Tags";
string myListName = "MyList";
string myTermName = "myTerm";
SPListItem myItem = web.Lists[myListName].GetItemById(1);
TaxonomyField tagsField = (TaxonomyField) myList.Fields[tagsFieldName];
TaxonomySession session = new TaxonomySession(site);
TermStore termStore = session.TermStores[tagsField.SspId];
TermSet termSet = termStore.GetTermSet(tagsField.TermSetId);
Term myTerm = null;
try
{
myTerm = termSet.Terms[myTermName];
}
catch (ArgumentOutOfRangeException)
{
// ?
}
string termString = String.Concat(myTerm.GetDefaultLabel(1033),
TaxonomyField.TaxonomyGuidLabelDelimiter, myTerm.Id);
if (tagsField.AllowMultipleValues)
{
TaxonomyFieldValueCollection tagsValues = new TaxonomyFieldValueCollection(tagsField);
tagsValues.PopulateFromLabelGuidPairs(
String.Join(TaxonomyField.TaxonomyMultipleTermDelimiter.ToString(),
new[] { termString }));
myItem[tagsFieldName] = tagsValues;
}
else
{
TaxonomyFieldValue tagValue = new TaxonomyFieldValue(termString);
myItem[tagsFieldName] = tagValue;
}
myItem.Update();
}
}
Related
I am in the process of creating a massive SSIS project to move the data from a series of Sharepoint Lists to a new Dynamics CRM Implementation (On-line). The SSIS package uses an OData Source to pull the data from the lists.
There are two columns on my list that do not show up on the OData call. These two columns have Multi-Select or Multi-Lookup values. REST services do not support multi-select choice fields. (Found on another thread: The methods you are attempting only work with choice columns that are NOT multi-select. Unfortunately, multi-select choice columns are not supported by the REST interface. Same holds true for SharePoint 2013 apparently.)
Therefore, I need to try to access the data in these columns on the SharePoint List using the OWSSVR.dll. The list I am having issues with gives me a "This page can’t be displayed" or a "This site can’t be reached" depending on the browser I use.
I verified the List ID by grabbing It from the list settings. Since it didn't work, I went to another SharePoint List I already migrated to verify the URL format. The other list works and brings back the data in XML format.
I am wondering if the OWSSVR.dll has the same restriction on Multi-select values. Any ideas or suggestions?
Psuedo URLS (restricted access site):
Works: http://dummy.sharepointSite.Com/cases/_vti_bin/owssvr.dll?Cmd=Display&List={b703d405-48c8-4211-9137-e1b50bdb0330}&XMLDATA=TRUE
Broken: http://dummy.sharepointSite.Com/cases/_vti_bin/owssvr.dll?Cmd=Display&List={8e148584-b5be-48f5-9343-85d23a7731cc}&XMLDATA=TRUE
I figured out a way to do this without using OWSSVR. I have to set up the User Context to the SharePoint site then retrieve the list of items.
Methods:
public static ClientContext SetupSPContext(string documentLibrarySiteURL,
string userName, string password, string domain = "")
{
ClientContext clientContext = new ClientContext(documentLibrarySiteURL);
SecureString pwString = new SecureString();
foreach (char c in password.ToCharArray()) { pwString.AppendChar(c); }
if (!String.IsNullOrWhiteSpace(domain))
clientContext.Credentials = new NetworkCredential(userName, pwString, domain);
else
clientContext.Credentials = new SharePointOnlineCredentials(userName, pwString);
Web web = clientContext.Web;
clientContext.Load(web);
clientContext.ExecuteQuery();
return clientContext;
}
public static ListItemCollection GetListItems(ClientContext context, string listName)
{
ListCollection listCollection = context.Web.Lists;
List targetList = context.Web.Lists.GetByTitle(listName);
CamlQuery query = new CamlQuery();
query.ViewXml = "<Query><OrderBy><FieldRef Name='fieldName' /></OrderBy></Query>";
ListItemCollection collListItem = targetList.GetItems(query);
context.Load(collListItem);
context.ExecuteQuery();
if (collListItem.Count == 0)
{
return null;
}
else
{
return collListItem;
}
}
Code in SSIS Script Component:
//In the PreExecute method:
// Variables were defined for the class and not in the preExecute Method
ClientContext spContext = SetupSPContext(Variables.spLocation, Variables.spUserName,
Variables.spPassword, Variables.spDomain);
ListItemCollection listItems = GetListItems(spContext, "List Name");
//Inside the Individual loop inside SSIS (Input0_ProcessInputRow):
ListItem listItem = GetListItem(listItems, fieldValue);
//Multiple Lookup Code
var values = (FieldLookupValue[])listItem["fieldName"];
var finalValue = "FieldName values: ";
if (values.Length > 0)
{
foreach (var value in values)
{
finalValue = value.LookupValue + "; ";
}
}
//Multiple Select
if (listItem["fieldName2"] != null)
{
var valuesTwo = (string[])listItem["fieldName2"];
string combinedValues = "fieldName2 Values: ";
foreach (var value in valuesTwo)
{
combinedValues += value + "; ";
}
}
has anyone managed to add a Web Part into a Wiki page using CSOM?
Background: Home.aspx is a Wiki page and all its WPs are located in the rich text zone (in fact a "WikiField" column). Technically they are located in a hidden "wpz" web part zone and in addition to this there is always a placeholder with WP's ID in the WikiField column.
I've modified the existing server-side code seen on http://blog.mastykarz.nl/programmatically-adding-web-parts-rich-content-sharepoint-2010/ and http://640kbisenough.com/2014/06/26/sharepoint-2013-moving-webparts-programmatically-to-rich-content-zone/ into this:
using System;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.WebParts;
public class Class1
{
void DeployWebPart(ClientContext clientContext, string zone, string pattern, string position)
{
List library = clientContext.Web.GetList("/sites/site/SitePages/");
clientContext.Load(library);
clientContext.ExecuteQuery();
CamlQuery query = CamlQuery.CreateAllItemsQuery(100);
ListItemCollection itemColl = library.GetItems(query);
clientContext.Load(itemColl, items => items.Include(i => i.Id, i => i.DisplayName, i => i["WikiField"]));
clientContext.ExecuteQuery();
ListItem item = itemColl.Where(i => i.DisplayName == "Home").First();
clientContext.ExecuteQuery();
File page = item.File;
LimitedWebPartManager lwm = page.GetLimitedWebPartManager(PersonalizationScope.Shared);
string xmlWebPart = #"<webParts>...</webParts>";
WebPartDefinition wpd = lwm.ImportWebPart(xmlWebPart);
WebPartDefinition realWpd = lwm.AddWebPart(wpd.WebPart, "wpz", 0);
List targetList = clientContext.Web.GetList("/sites/site/Announcements/");
clientContext.Load(targetList, l => l.Views);
clientContext.Load(realWpd);
clientContext.ExecuteQuery();
string wpId = String.Format("g_{0}", realWpd.Id.ToString().Replace('-', '_'));
if (zone == "wpz")
{
string htmlcontent = String.Format(CultureInfo.InvariantCulture, "<div class=\"ms-rtestate-read ms-rte-wpbox\" contenteditable=\"false\"><div class=\"ms-rtestate-notify ms-rtestate-read {0}\" id=\"div_{0}\"></div><div id=\"vid_{0}\" style=\"display:none;\"></div></div>", new object[] { realWpd.Id.ToString("D") });
string content = item["WikiField"] as string;
System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(System.Text.RegularExpressions.Regex.Escape(pattern));
if (position == "before")
{
content = regex.Replace(content, (htmlcontent + pattern), 1);
}
else
{
content = regex.Replace(content, (pattern + htmlcontent), 1);
}
item.Update();
clientContext.ExecuteQuery();
}
}
}
Everything works fine until the last item.Update() and clientContext.ExecuteQuery() are being invoked. Before the Update() the new placeholder gets properly inserted into the WikiField contents. But after the Update() the WikiField contents reverts back to its original state (!)
Note: As an alternative it is possible to add the WP into another zone (eg. "Bottom"). In this case the WP gets displayed on the page. But it has two major drawbacks: The newly created zone is not well formatted and the WP cannot be moved or even deleted.
Thanks for any input on this.
The following example demonstrates how to add web part into Enterprise Wiki page:
public static void AddWebPartIntoWikiPage(ClientContext context, string pageUrl, string webPartXml)
{
var page = context.Web.GetFileByServerRelativeUrl(pageUrl);
var webPartManager = page.GetLimitedWebPartManager(PersonalizationScope.Shared);
var importedWebPart = webPartManager.ImportWebPart(webPartXml);
var webPart = webPartManager.AddWebPart(importedWebPart.WebPart, "wpz", 0);
context.Load(webPart);
context.ExecuteQuery();
string marker = String.Format(CultureInfo.InvariantCulture, "<div class=\"ms-rtestate-read ms-rte-wpbox\" contentEditable=\"false\"><div class=\"ms-rtestate-read {0}\" id=\"div_{0}\"></div><div style='display:none' id=\"vid_{0}\"></div></div>", webPart.Id);
ListItem item = page.ListItemAllFields;
context.Load(item);
context.ExecuteQuery();
item["PublishingPageContent"] = marker;
item.Update();
context.ExecuteQuery();
}
Usage
var webPartXml = System.IO.File.ReadAllText(filePath);
using (var ctx = new ClientContext(webUri))
{
AddWebPartIntoWikiPage(ctx, wikiPageUrl,webPartXml);
}
Result
Is there a way to search/find a specific managed navigation term by guid using server object model
This code
var termGuid = new Guid(fieldValue.TermGuid);
var navTermSet = TaxonomyNavigation.GetTermSetForWeb(SPContext.Current.Web, StandardNavigationProviderNames.GlobalNavigationTaxonomyProvider, true);
if (navTermSet == null) return String.Empty;
var navTerm = navTermSet.Terms.Where(c => c.Id == termGuid).FirstOrDefault();
if (navTerm == null) return String.Empty;
return navTerm.GetWebRelativeFriendlyUrl();
only works if the term is in the first level, if its deeper down in hierarchy it doesn't find it and I'm not seeing any other mechanism that gets me a flat list of terms
It looks like a severe limitation with API
When you're looking for a certain term and you don't know how deep this resides and wether it's a parent or child, this becomes more complicated. If the API has no direct way of getting all the terms, regardless wether they're child or parent, I guess your best option is to list them all yourself by looping all terms + children and then find them by Guid.
Below code can be used to list all terms. The code is taken and shortened for your ease from this article.
List<Term> allTerms = new List<Term>();
void BuildTermList()
{
SPSite thisSite = SPContext.Current.Site;
TaxonomySession session = new TaxonomySession (thisSite);
foreach (TermStore termStore in session.TermStores)
{
foreach (Group group in termStore.Groups)
{
foreach (TermSet termSet in group.TermSets)
{
foreach (Term term in termSet.Terms)
{
AddTermSet(term);
}
}
}
}
}
void AddTermSet(Term term)
{
allItems.Add(term);
foreach (Term t in term.Terms)
AddTermSet(t);
}
Now that you have the list you can query it. Hope this helps.
Is there a way to programmatically access the Label & Value fields that has been created as a custom Field in MS CRM Dynamics please?
I have added a custom field called "new_producttypesubcode" which, for example, has 2 options, Trophy = 1000000 and Kit = 10000001.
I am writing an import utility that mirrors products between the customers website and their CRM and I want to get a list of all possible product options in the CRM to see if they are matched in the website.
So, in essence I want to...
get the list of possible new_producttypesubcodes and their corresponding values.
Iterate through the product variants in the website.
if the product variant name matches any name in the list of new_producttypecodes then add the value 1000000
So, if I find a product added to the website and its marked as a "Trophy" and "Trophy" exists in the CRM then new OptionSetValue(100000001)
I hope that makes sense...
Thanks
This function retrieves a dictionary of possible values localised to the current user. Taken from: CRM 2011 Programatically Finding the Values of Picklists, Optionsets, Statecode, Statuscode and Boolean (Two Options).
static Dictionary<String, int> GetNumericValues(IOrganizationService service, String entity, String attribute)
{
RetrieveAttributeRequest request = new RetrieveAttributeRequest
{
EntityLogicalName = entity,
LogicalName = attribute,
RetrieveAsIfPublished = true
};
RetrieveAttributeResponse response = (RetrieveAttributeResponse)service.Execute(request);
switch (response.AttributeMetadata.AttributeType)
{
case AttributeTypeCode.Picklist:
case AttributeTypeCode.State:
case AttributeTypeCode.Status:
return ((EnumAttributeMetadata)response.AttributeMetadata).OptionSet.Options
.ToDictionary(key => key.Label.UserLocalizedLabel.Label, option => option.Value.Value);
case AttributeTypeCode.Boolean:
Dictionary<String, int> values = new Dictionary<String, int>();
BooleanOptionSetMetadata metaData = ((BooleanAttributeMetadata)response.AttributeMetadata).OptionSet;
values[metaData.TrueOption.Label.UserLocalizedLabel.Label] = metaData.TrueOption.Value.Value;
values[metaData.FalseOption.Label.UserLocalizedLabel.Label] = metaData.FalseOption.Value.Value;
return values;
default:
throw new ArgumentOutOfRangeException();
}
}
So you would then need to do something like:
Dictionary<String, int> values = GetNumericValues(proxy, "your_entity", "new_producttypesubcode");
if(values.ContainsKey("Trophy"))
{
//Do something with the value
OptionSetValue optionSetValue = values["Trophy"];
int value = optionSetValue.Value;
}
Yes, that data is all stored in the metadata for an attribute (SDK article). You have to retrieve the entity metadata for the entity and then find the attribute in the list. Then cast that attribute to a PicklistAttributeMetadata object and it will contain a list of options. I would mention that typically retrieving Metadata from CRM is an expensive operation, so think about caching.
private static OptionSetMetadata RetrieveOptionSet(IOrganizationService orgService,
string entityName, string attributeName)
{
var entityResponse = (RetrieveEntityResponse)orgService.Execute(
new RetrieveEntityRequest
{ LogicalName = entityName, EntityFilters = EntityFilters.Attributes });
var entityMetadata = entityResponse.EntityMetadata;
for (int i = 0; i < entityMetadata.Attributes.Length; i++)
{
if (attributeName.Equals(entityMetadata.Attributes[i].LogicalName))
{
if (entityMetadata.Attributes[i].AttributeType.Value ==
AttributeTypeCode.Picklist)
{
var attributeMD = (PicklistAttributeMetadata)
entityMetadata.Attributes[i];
return attributeMD.OptionSet;
}
}
}
return null;
}
Here is how to write the options to the console using the above call.
var optionSetMD = RetrieveOptionSet(orgService, "account", "accountcategorycode");
var options = optionSetMD.Options;
for (int i = 0; i < options.Count; i++)
{
Console.WriteLine("Local Label: {0}. Value: {1}",
options[i].Label.UserLocalizedLabel.Label,
options[i].Value.HasValue ? options[i].Value.Value.ToString() : "null");
}
I believe this works for global option set attributes as well, but if you know it is a global option set there is a different message for it that would probably a bit more efficient (SDK article).
I'm working on an app that reuses some code from a previous solution.
The idea is to show a user all of the lists of a certain type in all the webs in a site collection so that the user can aggregate some data.
static public List<SPListMeta> AllSiteAnnouncementsLists(ClientContext clientContext)
{
var returnList = new List<SPListMeta>();
var per = new BasePermissions();
per.Set(PermissionKind.Open);
if (clientContext.Site.RootWeb.DoesUserHavePermissions(per).Value)
{
var rootWebLists = clientContext.Site.RootWeb.Lists;
returnList.AddRange(from List oList in rootWebLists
where oList.DoesUserHavePermissions(SPBasePermissions.ViewListItems) &&
where oList.BaseTemplate == (int)ListTemplateType.Announcements
select new SPListMeta(oList));
var collWebs =
clientContext.Site.OpenWebById(clientContext.Site.RootWeb.Id).GetSubwebsForCurrentUser(null);
foreach(Web oWeb in collWebs)
{
}
}
return returnList;
}
The critical line that does not work and does not seem to have a replacement is
oList.DoesUserHavePermissions(SPBasePermissions.ViewListItems)
as this has been removed from the CSOM List object
What's the new way to do this?
The new way to check for permissions is:
List.EffectiveBasePermissions.Has(PermissionKind.Open)