0

I am currently working a Farm Solution that is supposed to work with SharePoint 2016 and I am facing something that I can't understand. Maybe someone could explain this phenomenon or point what I am missing.

Basically, I deploy a List instance through a Web Feature. When the feature is activated, if the list is empty, I fill it with some values through the Feature Receiver. Now, I only want users that are Site Administrators to add, edit or delete items in this list. To achieve this, I created an Event Receiver for this list where I perform various checks. Everything seems to work as expected: when the feature is turned on, the list is created and populated (checks in the Event Receiver performed well at that moment) and only Site Administrators can add, edit or delete items in this list afterwards.

My code would be like the following:

// Feature Receiver
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    try
    {
        SPSecurity.RunWithElevatedPrivileges(delegate
        {
            SPWeb web = properties.Feature.Parent as SPWeb;
            SPList list = web.Lists.TryGetList("Information");

            if (list.Items.Count == 0)
            {
                SPListItem itemTitle = list.Items.Add();
                itemTitle["Title"] = "Name";
                itemTitle["SPWHInformationValue"] = "My Service";
                itemTitle["SPWHInformationDisplayFooter"] = true;
                itemTitle["SPWHInformationFooterOrder"] = 1;
                itemTitle.Update();
            }
        });
    }
    catch (Exception e)
    {
        // Logs
    }
}

// Event Receiver
public override void ItemAdding(SPItemEventProperties properties)
{
    try
    {
        base.ItemAdding(properties);

        string userLogin = properties.UserLoginName;
        SPSite site = properties.Site;
        SPWeb web = site.RootWeb;
        SPUser user = web.EnsureUser(userLogin);

        if (!user.IsSiteAdmin)
        {
            properties.ErrorMessage = "You don't have permission to perfom this action on list \"Information\".";
            properties.Status = SPEventReceiverStatus.CancelWithError;
        }
    }
    catch (Exception e)
    {
        // Logs
    }
}

Beside that, I wanted to create a small "framework" to avoid, as much as possible, code duplication and to ease unit testing. In a few words, I have various Services which are linked to one or more Repositories. To use a service in an Event Receiver, for example, I call an Accessor object that references the different services.

So, in the list Event Receiver, the code looks something like so:

public override void ItemAdding(SPItemEventProperties properties)
{
    try
    {
        base.ItemAdding(properties);

        if (!Accessor.ListService.IsUserAuthorizedToPerfomAction(properties))
        {
            properties.ErrorMessage = "You don't have permission to perfom this action on list \"Informations\".";
            properties.Status = SPEventReceiverStatus.CancelWithError;
            return;
        }
    }
    catch (Exception e)
    {
        // Logs
    }
}

Now, when this code is executed the first time, through the Feature Receiver, it works perfectly fine. But if it is executed a second time, when I try to add an item to list through the UI for example, I get a null reference exception for the SPWeb object. By setting a various breakpoints in Visual Studio, I can see that the SPWeb object still exists, but most of its properties are filled with "null reference exception" by that time.

I spent hours playing around with SPSecurity.RunWithElevatedPrivilege, trying to save the SPContext and the SPWeb object in properties, but each time, when the "EnsureUser" method is called for the second time, an exception is raised. I also noticed that if I disable the feature, empty the list and reactivate the feature, the same problem occurs.

I finally ended with some kind of workaround I don't really like.

Here is the last version of my code for my Services and my Repositories:

// ListService
public bool IsUserAuthorizedToPerfomAction(SPItemEventProperties properties)
{
    try
    {
        bool result = false;
        string userLogin = properties.UserLoginName;

        if (string.IsNullOrEmpty(userLogin))
        {
            return result;
        }

        SPSecurity.RunWithElevatedPrivileges(delegate
        {
            Accessor.EnsureContext(properties.Site, properties.Site.RootWeb);
            SPWHUser user = Accessor.UserService.GetUserByLoginName(userLogin);

            if (string.IsNullOrEmpty(user.LoginName) || user.ID == 0)
            {
                SPUser spUser = properties.Site.RootWeb.EnsureUser(userLogin);
                user = new SPWHUser
                {
                    ID = spUser.ID,
                    IsHiddenInUI = spUser.IsHiddenInUI,
                    IsSiteAdmin = spUser.IsSiteAdmin,
                    LoginName = spUser.LoginName
                };
            }

            if (user.IsSiteAdmin)
            {
                result = true;
            }
        });

        return result;
    }
    catch (Exception e)
    {
        // Logs
        return false;
    }
}

// UserService
public SPWHUser GetUserByLoginName(string loginName)
{
    try
    {
        if (string.IsNullOrEmpty(loginName))
        {
            return new SPWHUser();
        }

        SPUser user = (_repository as IUserRepository).EnsureUser(loginName);

        if (user == null)
        {
            return new SPWHUser();
        }

        return new SPWHUser
        {
            ID = user.ID,
            IsHiddenInUI = user.IsHiddenInUI,
            IsSiteAdmin = user.IsSiteAdmin,
            LoginName = user.LoginName
        };
    }
    catch (Exception e)
    {
        // Logs
        return new SPWHUser();
    }
}

// UserRepository
public SPUser EnsureUser(string loginName)
{
    if (string.IsNullOrEmpty(loginName))
    {
        return null;
    }

    if (_webRepository == null)
    {
        return null;
    }

    try
    {
        SPUser user = null;

        // !!! This line is the problem !!!
        user = _web.EnsureUser(loginName);

        return user;
    }
    catch (Exception ex)
    {
        // Logs
        return null;
    }
}

I cleaned the code a little bit, but the important part is here. The "_web" property in UserRepository is an SPWeb object. I also use my own struct where I save user's information (SPWHUser), mostly for Unit Testing.

I also ended with something really ugly (I think), in my Accessor object where I try to save the SPContext and the SPWeb:

public static void EnsureContext(SPSite site, SPWeb web)
{
    SPSecurity.RunWithElevatedPrivileges(delegate
    {
        if (Context != null)
        {
            return;
        }

        using (SPSite spSite = new SPSite(site.Url))
        {
            using (SPWeb spWeb = spSite.OpenWeb(web.ServerRelativeUrl))
            {
                Context = SPContext.GetContext(spWeb);

                (WebService.Repository as IWebRepository).Context = Context;
                (WebService.Repository as IWebRepository).Web = web;

                (SiteService.Repository as ISiteRepository).Context = Context;
                (SiteService.Repository as ISiteRepository).Site = site;
            }
        }
    });
}

Even with such a manipulation, the properties of the SPWeb object turn to "null reference exception" the second time. It is really strange, because the first time, the object contains the correct values and if I store it, it keeps all the values until the _web.EnsureUser(loginName) line is hit. Even if I "refresh" the object each time, it is the same scenario. However, properties.Site.RootWeb.EnsureUser(userLogin) works well, but if I place properties.Site.RootWeb in the "_web" property of my "WebRepository" and use it, it only works when the actions are operated through the Feature Receiver and not afterwards.

Can someone tell me what I do wrong or what I don't understand?

My thanks in advance.

0

One work around would be to assign permission to the list in the feature activated code or manually through list permissions instead of checking permission in item adding.

Ex:

list.BreakRoleInheritance(true);
SPGroup groupAdmin = web.SiteGroups["Site Administrators"];
SPRoleAssignment roleAssignmentAdmin = new SPRoleAssignment((SPPrincipal)groupAdmin);
SPRoleDefinition roleAdmin = web.RoleDefinitions.GetByType(SPRoleType.Administrator);
roleAssignmentAdmin.RoleDefinitionBindings.Add(roleAdmin);
list.RoleAssignments.Add(roleAssignmentAdmin);
list.Update();
  • Yes, it could be a workaround, but I still don't understand why the object suddenly loses its properties, like if it gets corrupted. – Lancelot Jan 3 at 6:48
  • It's not a good practice to keep SPWeb objects in the memory as a property. It's advised to disposing SPWeb objects properly to avoid unusual behaviors. Make sure to use "using (SPWeb web" always. Take a look on these best practices. docs.microsoft.com/en-us/previous-versions/office/developer/… – Prasanna Jan 3 at 17:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.