Table of Contents

From Web API

If the data is stored and retrieved through API's you can setup your events like the following code. This is an example how to handle the Create event and the Get All event with an external API.

using System.Linq.Expressions;
using System.Net;
using System.Text.Json;
using Hybrid_SaaS.Scim.Abstractions;
using Hybrid_SaaS.Scim.Abstractions.Operations;
using Hybrid_SaaS.Scim.DataTypes;
using Hybrid_SaaS.Scim.Entities;
using Hybrid_SaaS.Scim.Entities.User;
using Hybrid_SaaS.Scim.Extensions;
using Hybrid_SaaS.Scim.Utilities;

namespace YourApplication;

public class ScimUserEvents(IConfiguration configuration, IHttpClientFactory httpClientFactory)
	: IScimUserEvents,
		ICreate<ScimEnterpriseUser>,
		IReplace<ScimEnterpriseUser>,
		IUpdate<ScimEnterpriseUser>,
		IDelete
{
	public class User : IMappable<User, ScimEnterpriseUser>
	{
		public string Id { get; set; } = default!;
		public string UserName { get; set; } = default!;
		public bool ActivationProperty { get; set; }

		public class OrganizationData
		{
			public string? Department { get; set; }
		}

		public OrganizationData? Organization { get; set; }
		public static Expression<Func<User, ScimEnterpriseUser>> Mapper { get; } =
			user =>
				new()
				{
					Id = user.Id,
					UserName = user.UserName

					// ... other properties you want to map from the external model to the SCIM model
					// these mappings can be direct, from property to property, no null checks are required
				};
	}

	public async Task<OperationResult<ScimEnterpriseUser>> OnCreateAsync(ScimEnterpriseUser value, CancellationToken cancellationToken)
	{
		// Execute custom validation, use the client error when the client provided invalid data
		if (value.UserName == "invalid")
			return OperationResult.ClientError<ScimEnterpriseUser>("The username is 'invalid'");

		// You can use the asp.net core configuration to bind the configuration
		var mapper = new ScimObjectMapper();

		// Or you can retrieve configuration dynamicly from a JSON endpoint.
		//var mapperFromJson = ScimObjectMapper.FromJson("");
		configuration.GetRequiredSection("Mapper:ExampleConfiguration").Bind(mapper);

		// You can also set them in code
		mapper.Actions ??= new();
		mapper.Actions["active eq true"] = "ActivateUser";

		var result = await mapper.EvaluateAsync(value);

		// You can use the result to map to an object used for an API
		var user = result.Deserialize<User>(new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;

		// You can adjust some data based on actions
		user.ActivationProperty = mapper.OnExecuteAny("ActivateUser", value);

		var httpClient = httpClientFactory.CreateClient();
		var httpResponse = await httpClient.PostAsync(
			"/api/endpoint",
			// You can use any content type you want here
			new StringContent(""),
			cancellationToken
		);

		// Based on external responses, you can send a response back to the SCIM client.
		if (httpResponse.StatusCode == HttpStatusCode.NotFound)
			return OperationResult.NotFound<ScimEnterpriseUser>();

		if (!httpResponse.IsSuccessStatusCode)
			return OperationResult.ServerError<ScimEnterpriseUser>("API returned an error.");

		// If everything is ok, map your model properties to the SCIM model
		return OperationResult.Ok(
			new ScimEnterpriseUser
			{
				Id = user.Id,
				UserName = user.UserName,
				EnterpriseUser = new() { Department = user.Organization?.Department },

				// More properties ...
			}
		);
	}

	public async Task<OperationResult<ScimList<ScimEnterpriseUser>>> GetAllAsync(ScimParameters parameters, CancellationToken cancellationToken)
	{
		var httpClient = httpClientFactory.CreateClient();
		var httpResponse = await httpClient.GetAsync("/api/endpoint", cancellationToken);

		var collection = await httpResponse.Content.ReadFromJsonAsync<User[]>(cancellationToken);

		return OperationResult.Ok(
			collection!
				.AsQueryable()
				// Like this the collection will be filtered in memory based on the given SCIM parameters
				// When the QueryableProvider is from Entity Framework, the filter will be translated to SQL
				// If other QueryableProviders use LINQ translation, this can also be translated to other implementations
				.ApplyScimParameters<User, ScimEnterpriseUser>(parameters)
				.ToScimList(parameters)
		);
	}

	// ... implement other operations with web api calls here

	public Task<OperationResult<ScimEnterpriseUser>> GetAsync(string id, ScimParameters parameters, CancellationToken cancellationToken)
	{
		throw new NotImplementedException();
	}

	public Task<OperationResult<ScimEnterpriseUser>> OnReplaceAsync(string id, ScimEnterpriseUser value, CancellationToken cancellationToken)
	{
		throw new NotImplementedException();
	}

	public Task<OperationResult> OnUpdateAsync(string id, ScimEnterpriseUser value, CancellationToken cancellationToken)
	{
		throw new NotImplementedException();
	}

	public Task<OperationResult> OnDeleteAsync(string id, CancellationToken cancellationToken)
	{
		throw new NotImplementedException();
	}
}

💬 You can also use other data providers, like Entity Framework. Any ASP.NET Core data provider is supported.