SharePoint CSOM: How to update person or group field using ValidateUpdateListItem? - sharepoint

I am updating a SharePoint list item using the ValidateUpdateListItem method of the client-side object model to prevent creation of a new item version. This basically works fine for all fields except the ones with person or group field type. Does anyone know what is the correct string representation of a user or group value to be used as FieldValue of an ListItemFormUpdateValue object? I have already tried everything that seems reasonable to me (user ID from User Info, login name, lookup-value like combinations of these data etc.) without any success.

I just ran into a problem where updating more than 12 person or group fields with item update caused it to throw an exception. Apparently this is caused due to the list view look up threshold in SP online (12 as of this date).
http://blog.vanmeeuwen-online.nl/2012/07/value-does-not-fall-within-expected.html
To work around that I used the ValidateUpdateListItem method to update the person or group ids. The trick is to assign it a json in the format of
[{"Key":"i:0#.f|membership|user#yoursite.onmicrosoft.com"}]
formValues.Add(new ListItemFormUpdateValue() { FieldName = "AssignedTo", FieldValue = "[{'Key':'i:0#.f|membership|user#yoursite.onmicrosoft.com'}]" });
For multiple values, it can be comma separated. Have not tried it with group but i think it should work.
Hopefully this can be useful for someone.

Unfortunately ListItem.ValidateUpdateListItem method does not support the update of user field value. For example, in the following example AssignedTo field will not be updated:
using (var ctx = GetContext(webUri, userName, password))
{
var list = ctx.Web.Lists.GetByTitle(listTitle);
var item = list.GetItemById(itemId);
var formValues = new List<ListItemFormUpdateValue>();
formValues.Add(new ListItemFormUpdateValue() { FieldName = "Title", FieldValue = taskName});
formValues.Add(new ListItemFormUpdateValue() { FieldName = "AssignedTo", FieldValue = userId.ToString() }); //not supported
item.ValidateUpdateListItem(formValues, true, string.Empty);
ctx.ExecuteQuery();
}
Instead consider ListItem.Update Method to update user field value as demonstrated below:
using (var ctx = GetContext(webUri, userName, password))
{
var list = ctx.Web.Lists.GetByTitle(listTitle);
var item = list.GetItemById(itemId);
item["Title"] = taskName;
var assignedToValue = new FieldUserValue() { LookupId = userId };
var assignedToValues = new[] { assignedToValue };
item["AssignedTo"] = assignedToValues; //multi-valued user field
item.Update();
ctx.ExecuteQuery();
}

Related

Sitecore HOWTO: Search item bucket for items with specific values

I have an item bucket with more then 30 000 items inside. What I need is to quickly search items that have particular field set to particular value, or even better is to make something like SELECT WHERE fieldValue IN (1,2,3,4) statement. Are there any ready solutions?
I searched the web and the only thing I found is "Developer's Guide to Item
Buckets and Search" but there is no code examples.
You need something like this. The Bucket item is an IIndexable so it can be searched using Sitecore 7 search API.
This code snippet below can easily be adapted to meet your needs and it's just a question of modifying the where clause.if you need any further help with the sitecore 7 syntax just write a comment on the QuickStart blog post below and I'll get back to you.
var bucketItem = Sitecore.Context.Database.GetItem(bucketPath);
if (bucketItem != null && BucketManager.IsBucket(bucketItem))
{
using (var searchContext = ContentSearchManager.GetIndex(bucketItem as IIndexable).CreateSearchContext())
{
var result = searchContext.GetQueryable<SearchResultItem().Where(x => x.Name == itemName).FirstOrDefault();
if(result != null)
Context.Item = result.GetItem();
}
}
Further reading on my blog post here:
http://coreblimey.azurewebsites.net/sitecore-7-search-quick-start-guide/
Using Sitecore Content Editor:
Go to the bucket item then In search tab, start typing the following (replace fieldname and value with actual field name and value):
custom:fieldname|value
Then hit enter, you see the result of the query, you can multiple queries at once if you want.
Using Sitecore Content Search API:
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Linq;
using Sitecore.ContentSearch.SearchTypes;
using Sitecore.ContentSearch.Linq.Utilities
ID bucketItemID = "GUID of your bucket item";
ID templateID = "Guid of your item's template under bucket";
string values = "1,2,3,4,5";
using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
var predicate = PredicateBuilder.True<SearchResultItem>();
predicate = PredicateBuilder.And(item => item.TemplateId == new ID(templateID)
&& item.Paths.Contains(bucketItemID));
var innerPredicate = PredicateBuilder.False<SearchResultItem>();
foreach(string val in values.Split(','))
{
innerPredicate = PredicateBuilder.False<SearchResultItem>();
innerPredicate = innerPredicate.Or(item => item["FIELDNAME"] == val);
}
predicate = predicate.And(innerPredicate);
var result = predicate.GetResults();
List<Item> ResultsItems = new List<Item>();
foreach (var hit in result.Hits)
{
Item item = hit.Document.GetItem();
if(item !=null)
{
ResultsItems .Add(item);
}
}
}
The following links can give good start with the Search API:
http://www.fusionworkshop.co.uk/news-and-insight/tech-lab/sitecore-7-search-a-quickstart-guide#.VPw8AC4kWnI
https://www.sitecore.net/learn/blogs/technical-blogs/sitecore-7-development-team/posts/2013/06/sitecore-7-poco-explained.aspx
https://www.sitecore.net/learn/blogs/technical-blogs/sitecore-7-development-team/posts/2013/05/sitecore-7-predicate-builder.aspx
Hope this helps!

Determining if the value entered is a User or Group - Client Side Object Model

I'm having a play around with the client side object model and apps for SharePoint Online. I can retrieve the information from a Person and Groups field using a FieldUserValue object, however, how can I determine from this if the value entered is simply a user, or a SharePoint group?
As far as I can tell, the FieldUserValue only has a LookupId and LookupValue as its properties, which doesn't specify if it is a group or not. Have I gone the wrong way about this and is there a much better way of querying the field and checking if the value is a user of SharePoint group?
Thanks
You could determine whether the user field value is User or Group by getting Content Type of List Item in User Information List:
public static string GetUserFieldType(ClientContext ctx,FieldUserValue value)
{
var userInfoList = ctx.Site.RootWeb.SiteUserInfoList;
var userInfo = userInfoList.GetItemById(value.LookupId);
ctx.Load(userInfo,i => i.ContentType);
ctx.ExecuteQuery();
return userInfo.ContentType.Name;
}
Usage
Assume a List contains single-valued User Field, then:
using (var ctx = new ClientContext(webUrl))
{
ctx.Credentials = CreateSPOCredentials(userName, password);
var list = ctx.Web.Lists.GetByTitle(listTitle);
var listItem = list.GetItemById(itemId);
ctx.Load(listItem);
ctx.ExecuteQuery();
var userVal = listItem[fieldName] as FieldUserValue;
var type = GetUserFieldType(ctx,userVal);
var isUser = type == "Person";
var isGroup = type == "SharePointGroup";
}

List 'created by' in column

I know there is surveylist.get_description and surveylist.get_itemCount. I would like to know if there is a way to get created by fit in a column?
From syntax i guess you are using Javascriot CSOM.
Try this:
surveylist.get_author
Don't forget that you need to explicitly specify which properties you want to retrieve before you can get their values.
Unfortunately List Author property is not exposed via SharePoint CSOM.
How to retrieve List Author property via CSOM?
The idea is to retrieve SPList.SchemaXml property and extract Author property
function getListAuthor(listTitle,OnSuccess,OnError) {
var context = SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(listTitle);
context.load(web);
context.load(list,'SchemaXml');
context.executeQueryAsync(function(sender,args){
var schemaXml = $.parseXML(list.get_schemaXml());
var authorId = parseInt($(schemaXml).find('List').attr('Author')); //SchemaXml contains Author ID only
var listAuthor = web.getUserById(authorId);
context.load(listAuthor);
context.executeQueryAsync(OnSuccess(listAuthor),OnError);
},OnError);
}
//Usage
getListAuthor('Discussions List',
function(author){
console.log('List created by: ' + author.get_loginName())
},
function(sender,args){
console.log('Error occured:' + args.get_message());
}
);

Reconstructing an ODataQueryOptions object and GetInlineCount returning null

In an odata webapi call which returns a PageResult I extract the requestUri from the method parameter, manipulate the filter terms and then construct a new ODataQueryOptions object using the new uri.
(The PageResult methodology is based on this post:
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options )
Here is the raw inbound uri which includes %24inlinecount=allpages
http://localhost:59459/api/apiOrders/?%24filter=OrderStatusName+eq+'Started'&filterLogic=AND&%24skip=0&%24top=10&%24inlinecount=allpages&_=1376341370337
Everything works fine in terms of the data returned except Request.GetInLineCount returns null.
This 'kills' paging on the client side as the client ui elements don't know the total number of records.
There must be something wrong with how I'm constructing the new ODataQueryOptions object.
Please see my code below. Any help would be appreciated.
I suspect this post may contain some clues https://stackoverflow.com/a/16361875/1433194 but I'm stumped.
public PageResult<OrderVm> Get(ODataQueryOptions<OrderVm> options)
{
var incomingUri = options.Request.RequestUri.AbsoluteUri;
//manipulate the uri here to suit the entity model
//(related to a transformation needed for enumerable type OrderStatusId )
//e.g. the query string may include %24filter=OrderStatusName+eq+'Started'
//I manipulate this to %24filter=OrderStatusId+eq+'Started'
ODataQueryOptions<OrderVm> options2;
var newUri = incomingUri; //pretend it was manipulated as above
//Reconstruct the ODataQueryOptions with the modified Uri
var request = new HttpRequestMessage(HttpMethod.Get, newUri);
//construct a new options object using the new request object
options2 = new ODataQueryOptions<OrderVm>(options.Context, request);
//Extract a queryable from the repository. contents is an IQueryable<Order>
var contents = _unitOfWork.OrderRepository.Get(null, o => o.OrderByDescending(c => c.OrderId), "");
//project it onto the view model to be used in a grid for display purposes
//the following projections etc work fine and do not interfere with GetInlineCount if
//I avoid the step of constructing and using a new options object
var ds = contents.Select(o => new OrderVm
{
OrderId = o.OrderId,
OrderCode = o.OrderCode,
CustomerId = o.CustomerId,
AmountCharged = o.AmountCharged,
CustomerName = o.Customer.FirstName + " " + o.Customer.LastName,
Donation = o.Donation,
OrderDate = o.OrderDate,
OrderStatusId = o.StatusId,
OrderStatusName = ""
});
//note the use of 'options2' here replacing the original 'options'
var settings = new ODataQuerySettings()
{
PageSize = options2.Top != null ? options2.Top.Value : 5
};
//apply the odata transformation
//note the use of 'options2' here replacing the original 'options'
IQueryable results = options2.ApplyTo(ds, settings);
//Update the field containing the string representation of the enum
foreach (OrderVm row in results)
{
row.OrderStatusName = row.OrderStatusId.ToString();
}
//get the total number of records in the result set
//THIS RETURNS NULL WHEN USING the 'options2' object - THIS IS MY PROBLEM
var count = Request.GetInlineCount();
//create the PageResult object
var pr = new PageResult<OrderVm>(
results as IEnumerable<OrderVm>,
Request.GetNextPageLink(),
count
);
return pr;
}
EDIT
So the corrected code should read
//create the PageResult object
var pr = new PageResult<OrderVm>(
results as IEnumerable<OrderVm>,
request.GetNextPageLink(),
request.GetInlineCount();
);
return pr;
EDIT
Avoided the need for a string transformation of the enum in the controller method by applying a Json transformation to the OrderStatusId property (an enum) of the OrderVm class
[JsonConverter(typeof(StringEnumConverter))]
public OrderStatus OrderStatusId { get; set; }
This does away with the foreach loop.
InlineCount would be present only when the client asks for it through the $inlinecount query option.
In your modify uri logic add the query option $inlinecount=allpages if it is not already present.
Also, there is a minor bug in your code. The new ODataQueryOptions you are creating uses a new request where as in the GetInlineCount call, you are using the old Request. They are not the same.
It should be,
var count = request.GetInlineCount(); // use the new request that your created, as that is what you applied the query to.

Dynamics CRM 2011 - Getting too many rows back from a retrievemultiple on salesordersdetail

I have a plugin that is registered Update, Order, Post Operation. In the plugin I perform a retrievemultiple on the salesorderdetail. The problem I'm having is that there are 3 products that make up the order but I am returning 5 rows from the retrieve operation. I have added and deleted the same product multiple times during my testing and I'm not sure if that's what's causing the problem. I was thinking that after deleting a product from the order it may set a flag and get deleted after, but I don't see a status code or state code as an attribute. Why would it return too many rows?
Here is my code...
// Set the properties of the QueryExpression object.
orderDetailQuery.EntityName = "salesorderdetail";
orderDetailQuery.ColumnSet = orderDetailColumnSet;
EntityCollection salesOrderDetail = service.RetrieveMultiple(orderDetailQuery);
orderProductQuery.EntityName = "product";
orderProductQuery.ColumnSet = orderProductColumnSet;
foreach (var orderDetail in salesOrderDetail.Entities)
{
if(orderDetail.Attributes.Contains("productid"))
{
productGuid = ((EntityReference)orderDetail["productid"]).Id;
Entity product = service.Retrieve("product", productGuid, orderProductColumnSet);
}
}
Thank you for the help!!
The code you posted does not show you filtering for the specific Order.
I would expect that to retrieve all entities of that type in the system.
To filter, assuming you are using a QueryByAttribute, is to add an filter along the lines of:
var query = new QueryByAttribute();
query.EntityName = "salesorderdetail";
query.AddAttributeValue("orderid", orderId);//orderId is the Id of the parent Order
orderDetailQuery.EntityName = "salesorderdetail";
orderDetailQuery.ColumnSet = orderDetailColumnSet;
var results = service.RetrieveMultiple(query);
That way you are restricting your query to just products for the given order.
I'm not sure that your filtering is implemented. Here's a shot from the hip on how you could query for instances of SalesOrderDetail entity, fetching the values of fieldName1 and fieldName2 fields provided that the it's linked to the order with guid orderId.
QueryExpression query = new QueryExpression
{
EntityName = "salesorderdetail",
ColumnSet = new ColumnSet("fieldName1", "fieldName2"),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression
{
AttributeName = "orderid",
Operator = ConditionOperator.Equal,
Values = { orderId }
}
}
}
};

Resources