Implement a web service to delete orphaned user profiles

The following tutorial demonstrates creating a full implementation of a web service that takes care of removing orphaned UserProfile objects from Sitefinity CMS.

Define your DTOs and routes

The service will support deleting orphaned user profiles for a single user or for all users from a specified MembershipProvider. To do that you need to define two Request DTOs handling the delete by user ID and delete by providerName operations. Both Request DTO have the route specified. In this case the route is the same, but when used for deleting orphaned profiles for a single user it accepts a UserId parameter. The same route is used  for deleting all orphaned profiles for users under a particular membership provides, when a ProviderName parameter is passed.

using ServiceStack;
using System;
namespace SitefinityWebApp.DeleteOrphanedUserProfiles.Dto
{
[Route("/DeleteOrphanedUserProfile/{UserId}")]
public class DeleteOrphanedUserProfilesRequest : IReturn<DeleteOrphanedUserProfilesResponse>
{
public Guid UserId { get; set; }
}
[Route("/DeleteOrphanedUserProfiles/{ProviderName}")]
//You can also explicitly specify the Verb for the route
//For example: [Route("/DeleteOrphanedUserProfiles/{ProviderName}", "DELETE")]
public class DeleteAllOrphanedUserProfilesRequest : IReturn<DeleteAllOrphanedUserProfilesResponse>
{
public string ProviderName { get; set; }
}
}

You also need to define the matching Response DTOs:

using ServiceStack;
namespace SitefinityWebApp.DeleteOrphanedUserProfiles.Dto
{
public class DeleteOrphanedUserProfilesResponse
{
public string Result { get; set; }
//Add this property to leverage ServiceStack automatic exception handling
public ResponseStatus ResponseStatus { get; set; }
}
public class DeleteAllOrphanedUserProfilesResponse
{
public string Result { get; set; }
//Add this property to leverage ServiceStack automatic exception handling
public ResponseStatus ResponseStatus { get; set; }
}
}

Implement the service

In the service implementation you must implement two separate methods responsible for handling the business logic matching the respective Request DTO and route you already defined. The first method is responsible for deleting orphaned profiles for a single user, while the second one takes care of deleting orphaned profiles for all users in a specified membership provider.

using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
using Telerik.Sitefinity.Data;
using Telerik.Sitefinity.Security;
using Telerik.Sitefinity.Security.Model;
using SitefinityWebApp.DeleteOrphanedUserProfiles.Dto;
namespace SitefinityWebApp.DeleteOrphanedUserProfiles
{
public class DeleteOrphanedUserProfilesService : IService
{
// Any means the method accepts all verbs (e.g. GET, POST, PUT, etc.)
// You can explicitly specify which verb the method supports by naming it "Get", "Post", "Delete", etc.
public object Any(DeleteOrphanedUserProfilesRequest request)
{
if (request.UserId == Guid.Empty)
{
throw new ArgumentException("Invalid UserId");
}
string result;
var transactionName = string.Concat("DeleteUserProfileById_", Guid.NewGuid());
bool hasItemsToDelete = DeleteUserProfiles(transactionName, request.UserId);
if (hasItemsToDelete)
{
TransactionManager.CommitTransaction(transactionName);
result = $"Orphaned Profiles for user with Id {request.UserId} have been deleted";
}
else
{
result = $"No orphaned profiles for user with Id {request.UserId} have been found";
}
return new DeleteOrphanedUserProfilesResponse { Result = result };
}
public object Any(DeleteAllOrphanedUserProfilesRequest request)
{
var transactionName = string.Concat("DeleteAllOrphanedUserProfiles_", Guid.NewGuid());
string result;
bool hasItemsToDelete = DeleteUserProfiles(transactionName, Guid.Empty, request.ProviderName);
if (hasItemsToDelete)
{
TransactionManager.CommitTransaction(transactionName);
result = $"Orphaned Profiles for users in {request.ProviderName} membership provider have been deleted";
}
else
{
result = $"No orphaned profiles have been found";
}
return new DeleteAllOrphanedUserProfilesResponse { Result = result };
}
public bool DeleteUserProfiles(string transactionName, Guid userId, string membershipProviderName = "Default")
{
var providers = UserProfileManager.ProvidersCollection;
var hasItemsToDelete = false;
foreach (var provider in providers)
{
var manager = UserProfileManager.GetManager(provider.Name, transactionName);
var profiles = new List<UserProfile>();
if (userId != Guid.Empty)
{
profiles = manager.GetUserProfileLinks()
.Where(l => l.UserId == userId)
.Select(l => l.Profile)
.ToList();
}
else
{
profiles = manager.GetUserProfileLinks()
.Where(l => l.MembershipManagerInfo.ProviderName == membershipProviderName)
.Select(l => l.Profile)
.ToList();
}
foreach (var profile in profiles)
{
if (profile.User == null)
{
manager.Delete(profile);
hasItemsToDelete = true;
}
}
}
return hasItemsToDelete;
}
}
}

Implement the plugin and register the service

In the plugin you must implement the IPlugin interface and inside the Register method specify the type of the service you want to register:

using ServiceStack;
namespace SitefinityWebApp.DeleteOrphanedUserProfiles
{
public class DeleteOrphanedUserProfilesPlugin : IPlugin
{
/// <summary>
///Register the plugin in your Global.asax Application_Start()
/// For example: SystemManager.RegisterServiceStackPlugin(new SitefinityWebApp.DeleteOrphanedUserProfiles.DeleteOrphanedUserProfilesPlugin());
/// </summary>
public void Register(IAppHost appHost)
{
appHost.RegisterService(typeof(DeleteOrphanedUserProfilesService));
}
}
}

Once you have all the components ready you must register the plugin in your Global.asax:

using System;
using Telerik.Sitefinity.Services;
namespace SitefinityWebApp
{
public class RegisterServiceStackPlugin_Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
SystemManager.RegisterServiceStackPlugin(new SitefinityWebApp.DeleteOrphanedUserProfiles.DeleteOrphanedUserProfilesPlugin());
}
}
}

As a result, you can consume your new web service by calling it:

 

Want to learn more?

Increase your Sitefinity skills by signing up for our free trainings. Get Sitefinity-certified at Progress Education Community to boost your credentials.

Get started with Integration Hub | Sitefinity Cloud | Sitefinity SaaS

This free lesson teaches administrators, marketers, and other business professionals how to use the Integration hub service to create automated workflows between Sitefinity and other business systems.

Web Security for Sitefinity Administrators

This free lesson teaches administrators the basics about protecting yor Sitefinity instance and its sites from external threats. Configure HTTPS, SSL, allow lists for trusted sites, and cookie security, among others.

Foundations of Sitefinity ASP.NET Core Development

The free on-demand video course teaches developers how to use Sitefinity .NET Core and leverage its decoupled architecture and new way of coding against the platform.

Was this article helpful?