This issue arose when I got a requirement in office to build API for clients but a tricky one indeed as my philosophy is to always build systems that are highly configurable rather than a system that is subject to code changes every time a new requirement arises.
Hence I took my time to analyze and plan carefully how to come up with a system that can handle the scenario on the ground.
The problem statement is to build a web API that will be used by the various client but each of them requires a different level of data exposure.
Imagine a system to display customer information but the business team has decided that data expose to the client should be based on an agreement signed eg.
Client A can get [Name,Age,Sex] , Client B can get [Name,Age] only , Client C can get [Name] only.
Let us get into the raw action.
First, let us create a sample repository
public class DataRepository { public static IList<Data> GetData() { return new List<Data>() { new Data(){ Age = 45, Sex = "Male" , Name = "Adeyinka Oluwaseun", PhoneNumber = "1111"}, new Data(){ Age = 23, Sex = "Female" , Name = "Doyin Solomon", PhoneNumber = "2222"}, new Data(){ Age = 56, Sex = "Male" , Name = "John Doe", PhoneNumber = "3333"}, }; } } public class Data { public string Name { get; set; } public string Sex { get; set; } public int Age { get; set; } public string PhoneNumber { get; set; } }
Next is to create our custom response class which is a dictionary with an indexer to store the retrieved information from our repository.
public class Response { private readonly Dictionary<string, string> _store; public Response() { _store = new Dictionary<string, string>(); } public string this[string key] { get => _store[key]; set => _store[key] = value; } public Dictionary<string, string> Get() { return _store; } }
Next is to configure the clients in the appsettings.json where will configure the clients and the data that will be available to them based on the business agreement signed.
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "Clients": { "Banks": "CLIENTA:Name,Sex|CLIENTB:Name|CLIENTC:Name,Age,Sex" } }
Next is our controller class
[Route("api/[controller]")] public class InformationController : Controller { private static Clients _clients; public InformationController(IOptions<Clients> clients) { _clients = clients.Value; } [HttpGet()] public IActionResult Get([FromQuery]string number , [FromQuery]string clientName) { var response = new Response(); foreach (var info in ClientData()) { if (info.Key == clientName) { var props = info.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var result = DataRepository.GetData().FirstOrDefault(c => c.PhoneNumber == number); foreach (var prop in props) { if (result != null) { response[prop] = GetPropertyValue(result, prop); } } } } return new ObjectResult(JsonConvert.SerializeObject(response.Get(), Formatting.Indented)); } private static Dictionary<string, string> ClientData() { var clients = _clients.Banks.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); var clientInfo = new Dictionary<string, string>(); foreach (var client in clients) { var split = client.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); clientInfo.Add(split.First(), split.Last()); } return clientInfo; } private static string GetPropertyValue(Data.Data data, string property) { return data.GetType().GetProperty(property)?.GetValue(data, null).ToString() ?? string.Empty; } }
The model to bind to the appsettings.json
public class Clients { public string Banks { get; set; } }
Finally in the startup.cs where we bind the Clients in the appsettings.json to the Model class ie. Clients
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.Configure<Clients>(settings => Configuration.GetSection("Clients").Bind(settings)); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } }
Result screenshot from the test created out on different clients using POSTMAN.
CLIENT A
CLIENT C
CLIENT B