In this lesson, we’re going to build out the three remaining services quickly. We will continue to use the same patterns (DTOs, repositories, and service helper class) that we’ve used in the previous lessons. If you need to review any of the individual patterns, please review those lessons.
QuestTemplateService
1. Create the quests.json file in the SimpleRPG.Game.Service project and Data folder.
[
{
"Id": 1,
"Name": "Clear the herb garden",
"Description": "Defeat the snakes in the Herbalist's garden",
"RewardGold": 25,
"RewardXP": 10,
"RewardItems": [
{ "Id": 1002, "Qty": 1 }
],
"Requirements": [
{ "Id": 9001, "Qty": 5 }
]
}
]
2. Right click on the quests.json file and go to the properties for it. Set the Build Action for this file to “Embedded Resource”.
3. Create the IdQuantityItem
class in the SimpleRPG.Game.Services project and DTO folder. This class keeps information about quantities of particular items and will be used for quests, recipes, and traders.
namespace SimpleRPG.Game.Services.DTO
{
public class IdQuantityItem
{
public int Id { get; set; }
public int Qty { get; set; }
}
}
4. Create the QuestTemplate
class in the SimpleRPG.Game.Services project and DTO folder.
using System.Collections.Generic;
namespace SimpleRPG.Game.Services.DTO
{
public class QuestTemplate : NamedElement<int>
{
public string Description { get; set; } = string.Empty;
public IEnumerable<IdQuantityItem> Requirements { get; set; } = new List<IdQuantityItem>();
public int RewardGold { get; set; }
public int RewardXP { get; set; }
public IEnumerable<IdQuantityItem> RewardItems { get; set; } = new List<IdQuantityItem>();
}
}
5. Create the QuestTemplateMemoryRepositoryBuilder
class in the RepositoryBuilders.cs file (lines #71 – 82).
internal class QuestTemplateMemoryRepositoryBuilder : IRepositoryBuilder
{
private const string _resourceNamespace = "SimpleRPG.Game.Services.Data.quests.json";
public IRepository CreateRepository()
{
var entities = JsonSerializationHelper.DeserializeResourceStream<QuestTemplate>(_resourceNamespace);
var result = new InMemoryReadRepository<QuestTemplate, int>(entities);
return result;
}
}
6. Add the QuestTemplate
type mapping to the RepositoryFactory._resourceBuilderMapping
member (line #17).
using SimpleRPG.Game.Services.DTO;
using System;
using System.Collections.Generic;
namespace SimpleRPG.Game.Services.Repositories
{
public static class RepositoryFactory
{
private const string _repoResources = "resource";
private static readonly Dictionary<Type, IRepositoryBuilder> _resourceBuilderMapping =
new Dictionary<Type, IRepositoryBuilder>
{
{ typeof(ItemTemplate), new ItemTemplateMemoryRepositoryBuilder() },
{ typeof(MonsterTemplate), new MonsterTemplateMemoryRepositoryBuilder() },
{ typeof(LocationTemplate), new LocationTemplateMemoryRepositoryBuilder() },
{ typeof(TraderTemplate), new TraderTemplateMemoryRepositoryBuilder() },
{ typeof(QuestTemplate), new QuestTemplateMemoryRepositoryBuilder() },
{ typeof(RecipeTemplate), new RecipeTemplateMemoryRepositoryBuilder() },
};
public static IReadableRepository<T, TId> CreateRepository<T, TId>(string repoSource)
where T : NamedElement<TId>
where TId : struct
{
switch (repoSource)
{
case _repoResources:
return CreateResourceRepository<T, TId>();
default:
throw new ArgumentOutOfRangeException(nameof(repoSource));
}
}
private static IReadableRepository<T, TId> CreateResourceRepository<T, TId>()
where T : NamedElement<TId>
where TId : struct
{
var builder = _resourceBuilderMapping[typeof(T)];
return builder.CreateRepository() as IReadableRepository<T, TId>;
}
}
}
7. Create the QuestTemplateService
class in the SimpleRPG.Game.Service project and Services folder. In this service, we expose the same retrieval operations that we have in previous lessons.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using SimpleRPG.Game.Services.DTO;
using SimpleRPG.Game.Services.Repositories;
using System.Threading.Tasks;
namespace SimpleRPG.Game.Services.Services
{
public static class QuestTemplateService
{
private const string _baseRoute = "quest";
private static readonly IReadableRepository<QuestTemplate, int> _repo =
RepositoryFactory.CreateRepository<QuestTemplate, int>("resource");
private static readonly FunctionServiceHelper<QuestTemplate, int> _serviceHelper =
new FunctionServiceHelper<QuestTemplate, int>(_repo);
[FunctionName("GetQuestTemplates")]
public static async Task<IActionResult> GetQuestTemplates(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute)] HttpRequest req,
ILogger log)
{
return await _serviceHelper.GetEntities(req, log).ConfigureAwait(false);
}
[FunctionName("GetQuestTemplateCount")]
public static async Task<IActionResult> GetQuestTemplateCount(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute + "-count")]
HttpRequest req,
ILogger log)
{
return await _serviceHelper.GetEntityCount(req, log).ConfigureAwait(false);
}
[FunctionName("GetQuestTemplateById")]
public static async Task<IActionResult> GetQuestTemplateById(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute + "/{id}")]
HttpRequest req,
ILogger log,
int id)
{
return await _serviceHelper.GetEntityById(id, log).ConfigureAwait(false);
}
}
}
RecipeTemplateService
1. Create the recipes.json file in the SimpleRPG.Game.Service project and Data folder.
[
{
"Id": 1,
"Name": "Granola bar recipe",
"Ingredients": [
{ "Id": 3001, "Qty": 1 },
{ "Id": 3002, "Qty": 1 },
{ "Id": 3003, "Qty": 1 }
],
"OutputItems": [
{ "Id": 2001, "Qty": 1 }
]
}
]
2. Right click on the recipes.json file and go to the properties for it. Set the Build Action for this file to “Embedded Resource”.
3. Create the RecipeTemplate
class in the SimpleRPG.Game.Services project and DTO folder.
using System.Collections.Generic;
namespace SimpleRPG.Game.Services.DTO
{
public class RecipeTemplate : NamedElement<int>
{
public IEnumerable<IdQuantityItem> Ingredients { get; set; } = new List<IdQuantityItem>();
public IEnumerable<IdQuantityItem> OutputItems { get; set; } = new List<IdQuantityItem>();
}
}
4. Create the RecipeTemplateMemoryRepositoryBuilder
class in the RepositoryBuilders.cs file (lines #84-95).
internal class RecipeTemplateMemoryRepositoryBuilder : IRepositoryBuilder
{
private const string _resourceNamespace = "SimpleRPG.Game.Services.Data.recipes.json";
public IRepository CreateRepository()
{
var entities = JsonSerializationHelper.DeserializeResourceStream<RecipeTemplate>(_resourceNamespace);
var result = new InMemoryReadRepository<RecipeTemplate, int>(entities);
return result;
}
}
5. Add the RecipeTemplate
type mapping to the RepositoryFactory._resourceBuilderMapping
member (line #18).
using SimpleRPG.Game.Services.DTO;
using System;
using System.Collections.Generic;
namespace SimpleRPG.Game.Services.Repositories
{
public static class RepositoryFactory
{
private const string _repoResources = "resource";
private static readonly Dictionary<Type, IRepositoryBuilder> _resourceBuilderMapping =
new Dictionary<Type, IRepositoryBuilder>
{
{ typeof(ItemTemplate), new ItemTemplateMemoryRepositoryBuilder() },
{ typeof(MonsterTemplate), new MonsterTemplateMemoryRepositoryBuilder() },
{ typeof(LocationTemplate), new LocationTemplateMemoryRepositoryBuilder() },
{ typeof(TraderTemplate), new TraderTemplateMemoryRepositoryBuilder() },
{ typeof(QuestTemplate), new QuestTemplateMemoryRepositoryBuilder() },
{ typeof(RecipeTemplate), new RecipeTemplateMemoryRepositoryBuilder() },
};
public static IReadableRepository<T, TId> CreateRepository<T, TId>(string repoSource)
where T : NamedElement<TId>
where TId : struct
{
switch (repoSource)
{
case _repoResources:
return CreateResourceRepository<T, TId>();
default:
throw new ArgumentOutOfRangeException(nameof(repoSource));
}
}
private static IReadableRepository<T, TId> CreateResourceRepository<T, TId>()
where T : NamedElement<TId>
where TId : struct
{
var builder = _resourceBuilderMapping[typeof(T)];
return builder.CreateRepository() as IReadableRepository<T, TId>;
}
}
}
6. Create the RecipeTemplateService
class in the SimpleRPG.Game.Service project and Services folder.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using SimpleRPG.Game.Services.DTO;
using SimpleRPG.Game.Services.Repositories;
using System.Threading.Tasks;
namespace SimpleRPG.Game.Services.Services
{
public static class RecipeTemplateService
{
private const string _baseRoute = "recipe";
private static readonly IReadableRepository<RecipeTemplate, int> _repo =
RepositoryFactory.CreateRepository<RecipeTemplate, int>("resource");
private static readonly FunctionServiceHelper<RecipeTemplate, int> _serviceHelper =
new FunctionServiceHelper<RecipeTemplate, int>(_repo);
[FunctionName("GetRecipeTemplates")]
public static async Task<IActionResult> GetRecipeTemplates(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute)] HttpRequest req,
ILogger log)
{
return await _serviceHelper.GetEntities(req, log).ConfigureAwait(false);
}
[FunctionName("GetRecipeTemplateCount")]
public static async Task<IActionResult> GetRecipeTemplateCount(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute + "-count")]
HttpRequest req,
ILogger log)
{
return await _serviceHelper.GetEntityCount(req, log).ConfigureAwait(false);
}
[FunctionName("GetRecipeTemplateById")]
public static async Task<IActionResult> GetRecipeTemplateById(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute + "/{id}")]
HttpRequest req,
ILogger log,
int id)
{
return await _serviceHelper.GetEntityById(id, log).ConfigureAwait(false);
}
}
}
TraderTemplateService
1. Create the traders.json file in the SimpleRPG.Game.Service project and Data folder.
[
{
"Id": 101,
"Name": "Susan",
"Inventory": [
{ "Id": 1001, "Qty": 1 }
]
},
{
"Id": 102,
"Name": "Farmer Ted",
"Inventory": [
{ "Id": 1001, "Qty": 1 }
]
},
{
"Id": 103,
"Name": "Pete the Herbalist",
"Inventory": [
{ "Id": 1001, "Qty": 1 }
]
}
]
2. Right click on the traders.json file and go to the properties for it. Set the Build Action for this file to “Embedded Resource”.
3. Create the TraderTemplate
class in the SimpleRPG.Game.Services project and DTO folder.
using System.Collections.Generic;
namespace SimpleRPG.Game.Services.DTO
{
public class TraderTemplate : NamedElement<int>
{
public IEnumerable<IdQuantityItem> Inventory { get; set; } = new List<IdQuantityItem>();
}
}
4. Create the TraderTemplateMemoryRepositoryBuilder
class in the RepositoryBuilders.cs file (lines #58-69).
internal class TraderTemplateMemoryRepositoryBuilder : IRepositoryBuilder
{
private const string _resourceNamespace = "SimpleRPG.Game.Services.Data.traders.json";
public IRepository CreateRepository()
{
var entities = JsonSerializationHelper.DeserializeResourceStream<TraderTemplate>(_resourceNamespace);
var result = new InMemoryReadRepository<TraderTemplate, int>(entities);
return result;
}
}
5. Add the TraderTemplate
type mapping to the RepositoryFactory._resourceBuilderMapping
member (line #16).
using SimpleRPG.Game.Services.DTO;
using System;
using System.Collections.Generic;
namespace SimpleRPG.Game.Services.Repositories
{
public static class RepositoryFactory
{
private const string _repoResources = "resource";
private static readonly Dictionary<Type, IRepositoryBuilder> _resourceBuilderMapping =
new Dictionary<Type, IRepositoryBuilder>
{
{ typeof(ItemTemplate), new ItemTemplateMemoryRepositoryBuilder() },
{ typeof(MonsterTemplate), new MonsterTemplateMemoryRepositoryBuilder() },
{ typeof(LocationTemplate), new LocationTemplateMemoryRepositoryBuilder() },
{ typeof(TraderTemplate), new TraderTemplateMemoryRepositoryBuilder() },
{ typeof(QuestTemplate), new QuestTemplateMemoryRepositoryBuilder() },
{ typeof(RecipeTemplate), new RecipeTemplateMemoryRepositoryBuilder() },
};
public static IReadableRepository<T, TId> CreateRepository<T, TId>(string repoSource)
where T : NamedElement<TId>
where TId : struct
{
switch (repoSource)
{
case _repoResources:
return CreateResourceRepository<T, TId>();
default:
throw new ArgumentOutOfRangeException(nameof(repoSource));
}
}
private static IReadableRepository<T, TId> CreateResourceRepository<T, TId>()
where T : NamedElement<TId>
where TId : struct
{
var builder = _resourceBuilderMapping[typeof(T)];
return builder.CreateRepository() as IReadableRepository<T, TId>;
}
}
}
6. Create the TraderTemplateService
class in the SimpleRPG.Game.Service project and Services folder.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using SimpleRPG.Game.Services.DTO;
using SimpleRPG.Game.Services.Repositories;
using System.Threading.Tasks;
namespace SimpleRPG.Game.Services.Services
{
public static class TraderTemplateService
{
private const string _baseRoute = "trader";
private static readonly IReadableRepository<TraderTemplate, int> _repo =
RepositoryFactory.CreateRepository<TraderTemplate, int>("resource");
private static readonly FunctionServiceHelper<TraderTemplate, int> _serviceHelper =
new FunctionServiceHelper<TraderTemplate, int>(_repo);
[FunctionName("GetTraderTemplates")]
public static async Task<IActionResult> GetTraderTemplates(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute)] HttpRequest req,
ILogger log)
{
return await _serviceHelper.GetEntities(req, log).ConfigureAwait(false);
}
[FunctionName("GetTraderTemplateCount")]
public static async Task<IActionResult> GetTraderTemplateCount(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute + "-count")]
HttpRequest req,
ILogger log)
{
return await _serviceHelper.GetEntityCount(req, log).ConfigureAwait(false);
}
[FunctionName("GetTraderTemplateById")]
public static async Task<IActionResult> GetTraderTemplateById(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = _baseRoute + "/{id}")]
HttpRequest req,
ILogger log,
int id)
{
return await _serviceHelper.GetEntityById(id, log).ConfigureAwait(false);
}
}
}
Validate Services
With the code for all of the service complete, we can now build our Azure Functions services. When we run the services, we will see all of the service endpoints available in our local Azure Functions runtime command line.

We can take any of the new endpoint urls and validate the new services return the data we expect. Here’s what we get when we try to retrieve all quests (localhost:7071/api/quest):

Next let’s test the recipes returned (localhost:7071/api/recipe):

Finally, let’s retrieve the traders in the game (localhost:7071/api/trader):

With this update, we have created service endpoints for all of the game data that we have defined in the game thus far. We will be able to replace the local files in our game, with service calls to get the game data. This will separate our game runtime from the game data. And make it easier for us to provide updated game content without having to update the game code. Our services are still very simple and don’t use any good read-write storage systems yet, but it’s a start down that path, which we will explore in future lessons.
In in the next couple of lessons, we are going to create continuous integration (CI) and continuous delivery (CD) pipelines for the Azure Functions code… just like we did earlier in the chapter for the game website.