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:
- For a single user: http://yoursite.com/restapi/DeleteOrphanedUserProfile/2CC975E5-27A2-4275-8AEB-C3413D19FD67
- For all users in a membership provider: http://yoursite.com/restapi/DeleteOrphanedUserProfiles/default