As my work on PlayFabBuddy continues, I’m identifying more ways to better use the PlayFab SDK. One thing I started thinking about was how to use the PlayFab C# SDK and use it with Dependency Injection. Doing this lead me to some realizations that are good to know if you are using Azure Functions (or any other server side Code) that need to work with PlayFab, for example authenticating a User with PlayFab before it can access your custom API.
PlayFab and Dependency Injection
In software development the practice of Dependency Injection is well known and documented. We use it to loosely couple dependencies and achieve the inversion of control in our code.
Now if you are Using the PlayFab SDK the documentation leads you to the Static Classes within PlayFab. Using these gets you static access to the PlayFab APIs without any control over the used HTTPClient.
PlayFabSettings.staticSettings.TitleId = TitleId;
PlayFabSettings.staticSettings.DeveloperSecretKey = DeveloperSecret;
var getTitleEntityTokenRequest = new GetEntityTokenRequest(); //Do not need to set Entity
var titleEntityResponse = await PlayFabAuthenticationAPI.GetEntityTokenAsync(getTitleEntityTokenRequest);
//If Login was not Successfull
if (titleEntityResponse.Result == null)
{
throw new PlayFabException(PlayFabExceptionCode.NotLoggedIn,
"Can't authenticate with Developer Secret for titleId: " + TitleId);
}
Doing this in your code is breaking with the principels of SOLID and therefore makes your code harder to read and maintain.
Why is this bad in the real world?
To make this more practical this is bad practice if you are using PlayFab within Azure Functions or any other managed environment. Because this will cause your managed environment to create a non-optimized version of HTTPClient that might cause too many outbound connections.
How you usually deal with these things in functions is that you are declaring the dependency outside of the scope so it will be reused by the instance.
Instance Classes to the rescue!
For every static class in the PlayFab C# SDK you are provided with an instantiated version of it. This is helpful because it allows you to use it for Dependency injection making your code cleaner and confirm with SOLID. Not just that it also solves the outbound connection in your function app.
Here is an example of how PlayFabBuddy uses this with the .NET Dependency Injection container:
services.AddSingleton<PlayFabMultiplayerInstanceAPI>();
Object Lifetime
Now in this container you have the option to declare different life cycles of your object. In this case Singleton was used to optimize for the HTTPClient being used in this to be only created once and therefore reuse open connections. Transient would create a new instance for every dependency which would be inefficient.
For a web context (ASP.NET Core) you might want to consider Scoped lifetime. As this would allow the Object to be reused within the same request cycle multiple times.
Some of the Instance APIs to require a context object that is specific to a user or admin entity to work. Even though this is sometimes required at creation time these context objects can be overwritten in any method!
PlayFabSettings.staticSettings.TitleId = TitleId;
PlayFabSettings.staticSettings.DeveloperSecretKey = DeveloperSecret;
var getTitleEntityTokenRequest = new GetEntityTokenRequest(); //Do not need to set Entity
var titleEntityResponse = await PlayFabAuthenticationAPI.GetEntityTokenAsync(getTitleEntityTokenRequest);
//If Login was not Successfull
if (titleEntityResponse.Result == null)
{
throw new PlayFabException(PlayFabExceptionCode.NotLoggedIn,
"Can't authenticate with Developer Secret for titleId: " + TitleId);
}
var adminEntityToken = titleEntityResponse.Result.EntityToken;
services.AddSingleton(new PlayFabAuthenticationContext { EntityToken = adminEntityToken });
services.AddSingleton<PlayFabMultiplayerInstanceAPI>();
public class MatchmakingAdapter : IMatchmakingAdapter
{
private readonly PlayFabMultiplayerInstanceAPI _api;
public MatchmakingAdapter(PlayFabMultiplayerInstanceAPI api)
{
_api = api;
}
public async Task CreateQueueAsync(QueueConfigEntity queueConfig)
{
var createQueueRequest = new SetMatchmakingQueueRequest
{
MatchmakingQueue = new MatchmakingQueueConfig
{
Name = queueConfig.Name,
MinMatchSize = queueConfig.MinMatchSize,
MaxMatchSize = queueConfig.MaxMatchSize
}
};
var result = await _api.SetMatchmakingQueueAsync(createQueueRequest);
if (result.Error != null)
{
throw new Exception(result.Error.ToString());
}
}
}
Conclusion
This helps you to get cleaner code and the power to work around missing configuration options in the SDK for HTTPClient we are using the object Lifecyle instead to finetune for our needs and performance.