Please note: This blog is no longer active. My new blog is located at http://blog.timwheeler.io

Sunday, March 26, 2017

Sorry, the operation failed because another operation was updating WOPI bindings at the same time

Configuring SharePoint 2013 for Office Web Apps. While trying to configure the bindings, I would get the following error: "Sorry, the operation failed because another operation was updating WOPI bindings at the same time" This happened any time I tried to modify the bindings, such as Remove-SPWOPIBinding -all:$true. Initially I tried rebuilding the OWA server based on this page: https://blogs.technet.microsoft.com/office_web_apps_server_2013_support_blog/2013/12/20/office-web-apps-2013-rebuild-your-farm-in-a-few-easy-steps/ Unfortunately that didn't work. The resolution was to clear the Config cache across the SharePoint farm: http://alen.malhasoglu.com/2012/01/03/updatedconcurrencyexception-on-sharepoint/ Thanks to Alen Malhasoglu for that one :)

Sunday, May 17, 2015

Using ELMAH with Couchbase NetClient 2.1

Originally I tried the nuget package for Elmah integration with Couchbase. However it was using an earlier version of the NetClient and I was having some trouble getting it going. After grabbing the code from git hub (https://github.com/couchbaselabs/elmah-couchbase)
 I managed to update it for the new SDK syntax.
I also needed a custom json converter for the NameValueCollection type due to a deserialization error I received.

Check out the converter here: NameValueCollection Json Converter


My web.config
   <sectionGroup name="couchbaseClients">
      <section name="couchbase"
               type="Couchbase.Configuration.Client.Providers.CouchbaseClientSection, Couchbase.NetClient"/>
    </sectionGroup>
  </configSections>
  <couchbaseClients>
    <couchbase useSsl="false">
      <servers>
        <add uri="http://localhost:8091/pools"></add>
      </servers>
      <buckets>
        <add name="default" useSsl="false">
          <connectionPool name="custom" maxSize="10" minSize="5"></connectionPool>
        </add>
      </buckets>
    </couchbase>
  </couchbaseClients>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
  </startup>

...
 <elmah>
    <errorLog type="MyNameSpace.CouchbaseErrorLog, MyAssembly" couchbaseConfigSection="couchbaseClients/couchbase"  />
  </elmah>

CouchBaseErrorLog.cs
public class CouchbaseErrorLog : ErrorLog
    {
        private static ClientConfiguration _client;
        private static Cluster _cluster;
        private readonly string _elmahBucketName = "default";

        /// <summary>
        ///     Initialize new instance of CouchbaseClient using either default config
        ///     or specified configuration name
        /// </summary>
        public CouchbaseErrorLog(IDictionary config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (_client == null)
            {
                if (config.Contains("couchbaseConfigSection"))
                {
                    _client = CreateCouchbaseClient(config["couchbaseConfigSection"] as string);
                }
                else
                {
                    _client = CreateCouchbaseClient();
                }

                if (config.Contains("applicationName"))
                {
                    ApplicationName = config["applicationName"] as string;
                }
                _cluster = new Cluster(_client);
                SetNameValueConverter();
                    //This fixes issue with NameValueCollection deserialization and the serverVaribles property.
            }
        }

        /// <summary>
        ///     Name displayed in ELMAH viewer
        /// </summary>
        public override string Name
        {
            get { return "Couchbase Server Error Log"; }
        }

        public static ClientConfiguration CreateCouchbaseClient(string couchbaseConfigSection = null)
        {
            if (couchbaseConfigSection != null)
            {
                var config = ConfigurationManager.GetSection(couchbaseConfigSection) as CouchbaseClientSection;
                if (config == null)
                    throw new ArgumentException("Couchbase config section " + couchbaseConfigSection + " not found.");

                return new ClientConfiguration(config);
            }

            return new ClientConfiguration();
        }

        private static void SetNameValueConverter()
        {
            var settings = new JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
                TypeNameHandling = TypeNameHandling.All,
                Formatting = Formatting.Indented
            };
            settings.Converters.Add(new NameValueCollectionConverter());
            JsonConvert.DefaultSettings = () => settings;
        }

        /// <summary>
        ///     Get error log entry by id
        /// </summary>
        public override ErrorLogEntry GetError(string id)
        {
            using (var bucket = _cluster.OpenBucket(_client.BucketConfigs[_elmahBucketName].BucketName))
            {
                return GetError(bucket, id);
            }
        }

        private ErrorLogEntry GetError(IBucket bucket, string id)
        {
            if (string.IsNullOrEmpty(id))
            {
                throw new ArgumentNullException("id");
            }
            try
            {
                Guid.Parse(id);
            }
            catch (FormatException e)
            {
                throw new ArgumentException(e.Message, "id", e);
            }
            var document = bucket.Get<string>(id);
            if (document.Success)
            {
                var error = JsonConvert.DeserializeObject<Error>(document.Value);
                return new ErrorLogEntry(this, id, error);
            }
            return null;
        }

        /// <summary>
        ///     Get list of errors for view in Elmah web viewer
        /// </summary>
        public override int GetErrors(int pageIndex, int pageSize, IList errorEntryList)
        {
            if (pageIndex < 0)
                throw new ArgumentOutOfRangeException("pageIndex", pageIndex, null);

            if (pageSize < 0)
                throw new ArgumentOutOfRangeException("pageSize", pageSize, null);

            var skip = pageSize*pageIndex;

            //this is NOT the most efficient way to page in Couchbase/CouchDB, but is necessary because
            //there is no way to keep state of the startkey between requests
            //see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-writing-querying-pagination.html
            //for more information
            using (var bucket = _cluster.OpenBucket(_client.BucketConfigs[_elmahBucketName].BucketName))
            {
                var query = bucket.CreateQuery("errors", "by_date", false).Desc().Skip(skip).Limit(pageSize);

                var view = bucket.Query<dynamic>(query);

                foreach (var item in view.Rows)
                {
                    var errorLogEntry = GetError(bucket, item.Id);
                    if (errorLogEntry != null)
                    {
                        errorEntryList.Add(errorLogEntry);
                    }
                }

                return view.Rows.Count();
            }
        }

        /// <summary>
        ///     Log an error
        /// </summary>
        public override string Log(Error error)
        {
            if (error == null)
                throw new ArgumentNullException("error");

            var id = Guid.NewGuid().ToString();
            using (var bucket = _cluster.OpenBucket(_client.BucketConfigs[_elmahBucketName].BucketName))
            {
                // var errorJson = JsonConvert.SerializeObject(error);
                var errorJson = JsonConvert.SerializeObject(error);
                var result = bucket.Insert(id, errorJson);
#if DEBUG
                if (!result.Success && Debugger.IsAttached)
                {
                    Debugger.Break();
                }
#endif
            }
            return id;
        }
    }
View
One last thing, I need to modify the couchbase view.
//view code for Elmah viewer. Must be named "by_date" and located in design document named "errors" without the quotes.
function (doc) {
if (doc.exception) {
emit(doc.time, null);
}
}

Wednesday, December 10, 2014

Create Security Groups and Assign Permissions in SharePoint 2013

Here is a fun little helper for creating security groups with a permission assignment.
I use the word fun because I have been working with SharePoint too long and my brain now works in reverse.


/// <summary>
    /// Security Helper
    /// </summary>
    public class SecurityHelper
    {
        public static bool GroupExists(SPGroupCollection groups, string name)
        {
         
            if (String.IsNullOrEmpty(name) || (name.Length > 255) ||(groups == null) || (groups.Count == 0))
            {
                return false;
            }
            return groups.Cast<SPGroup>().FirstOrDefault(t => t.Name == name) != null;
        }
        /// <summary>
        /// Creates a group if it does not exist.  It will also assign a permission level to the group.
        /// </summary>
        /// <param name="web"></param>
        /// <param name="groupName"></param>
        /// <param name="permissionLevel"></param>
        /// <param name="description"></param>
        /// <param name="owner"></param>
        /// <returns>The Group</returns>
        public static SPMember CreateGroup(SPWeb web, string groupName, string permissionLevel, string description, SPMember owner = null)
        {
            string uniqueGroupName = String.Format("{0} - {1}", web.Name, groupName);
            if (!GroupExists(web.SiteGroups, uniqueGroupName))
            {
                var role = web.RoleDefinitions.Cast<SPRoleDefinition>().FirstOrDefault(t => t.Name == permissionLevel);
                if (role == null)
                {
                    throw new KeyNotFoundException(String.Format("The Security Permission level {0} does not exist at web {1}.", permissionLevel, web.Url));
                }
                if (owner == null)
                {
                    owner = web.CurrentUser;
                }
                web.SiteGroups.Add(uniqueGroupName, owner, null, description);
                SPGroup group = web.SiteGroups.GetByName(uniqueGroupName);
                var assignment = new SPRoleAssignment(@group);
                assignment.RoleDefinitionBindings.Add(role);
                web.RoleAssignments.Add(assignment);
            }
            return web.SiteGroups.GetByName(uniqueGroupName);
        }
    }

Breaking permissions with SharePoint 2013 & CSOM

Lately I've been doing a lot of work with the Client Side Object Model for SharePoint 2013.  While I have been able to do most things we used to, in the dark days of server side development. However one thing didn't seem to work right, Web.BreakRoleInheritance(bool, bool).
Along the lines of:

web.BreakRoleInheritance(true, true);
clientContext.ExecuteQuery();

Passing the first parameter states the call should break permissions at the web level and copy existing RoleAssignments.  However, I could not get this to work.  Every time the assignments where gone.  Regardless of the values I passed.

The fallback option was to devolve, create a feature with server side code, which looks pretty much identical.  I then call the feature activation automatically through my CSOM service.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                var web = (SPWeb) properties.Feature.Parent;
                SPSecurity.RunWithElevatedPrivileges(() =>
                {
                    using (var site = new SPSite(web.Site.ID))
                    {
                        using (SPWeb elevatedWeb = site.OpenWeb(web.ID))
                        {
                            elevatedWeb.BreakRoleInheritance(true, true);
                        }
                    }
                   
                });
            }
            catch (Exception ex)
            {
                LogService.LogException(LogCategory.FeatureReceiver, ex);
                throw;
            }
        }

And that worked as expected, role assignments had been copied through from the parent.


Wednesday, August 13, 2014

Search service lost SQL permission

Summary
While working with Result Sources in a site collection, I got that wonderful "Something went wrong" error. The ULS showed a bunch of SQL execute permission errors on the search database.  The solution was to update the SQL permissions for the search service.  It was not obvious why the permissions where lost in the first place.

Symptoms
ULS Error events such as:
System.Data.SqlClient.SqlException (0x80131904): The EXECUTE permission was denied on the object 'proc_MSS_GetLease', database 'SP2013_DEV__SA_Search'
…and other SQL permission errors.

Fix
Grant SPSearchDBAdmin role to the search service account on the Search database.  (SQL Server Permissions)


Thoughts
Just before this occurred I had updated permissions of the Search Service Application in Central Admin.  I had added my user account.  Can't see how this would have caused it however.

Thursday, May 15, 2014

Content classification approach for SharePoint 2013 / Office365

I had a request to implement a classification system for SharePoint 2013 sites.  This needed to work with Office 365 and On Premise so farm solutions are a no go.  My company gave permission to make this public so here goes.

The process to use this consists of:
  1. Update property bag values on your site collection's root web
  2. Upload the Classify.js to the site collection
  3. Add a reference to the JS in the master page

For all the detailed information and downloadable scripts visit my Wiki.



Sunday, March 9, 2014

TimeStamps with Entity Framework

So I've been building an MVC 5, EF6 solution with Code First Migrations.
Got what I think is a great structure of abstraction thanks to Le Long's Post.

One thing that wasn't obvious (to me at least), was how to handle concurrency with EF and the Scaffolded Views.
When editing an object, the TimeStamp was never returned, and of course EF would throw a "DbUpdateConcurrencyException".

Here is a solution that worked for me:

  1. Create a base entity class with a TimeStamp on it
  2. Add the attributes: [Timestamp, ScaffoldColumn(false)]
  3. Edit the MVC View and add a hidden field for the timestamp


Base Class and Attributes
I defined a base class for all entities requiring concurrency.
public abstract class PersistedEntity : EntityBase
{
        [Timestamp, ScaffoldColumn(false)]
        public byte[] TimeStamp { get; set; }
}

MVC View
I found that the view did not by itself return the TimeStamp with the model when posting data back to the server.  So I added this after the @Html.BeginForm():
@Html.HiddenFor(model => model.TimeStamp)

It was then returned back to the server, and the entity could be saved correctly.