@ -0,0 +1,2 @@ | |||||
docker-compose.yml | |||||
Dockerfile |
@ -0,0 +1,12 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Services.Basket.API | |||||
{ | |||||
public class BasketSettings | |||||
{ | |||||
public string ConnectionString { get; set; } | |||||
} | |||||
} |
@ -0,0 +1,44 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers | |||||
{ | |||||
//NOTE: Right now this is a very chunky API, as the app evolves it is possible we would | |||||
//want to make the actions more fine graned, add basket item as an action for example. | |||||
//If this is the case we should also investigate changing the serialization format used for Redis, | |||||
//using a HashSet instead of a simple string. | |||||
[Route("/")] | |||||
public class BasketController : Controller | |||||
{ | |||||
private IBasketRepository _repository; | |||||
public BasketController(IBasketRepository repository) | |||||
{ | |||||
_repository = repository; | |||||
} | |||||
// GET api/values/5 | |||||
[HttpGet("{id}")] | |||||
public async Task<CustomerBasket> Get(Guid id) | |||||
{ | |||||
return await _repository.GetBasket(id); | |||||
} | |||||
// POST api/values | |||||
[HttpPost] | |||||
public void Post([FromBody]CustomerBasket value) | |||||
{ | |||||
_repository.UpdateBasket(value); | |||||
} | |||||
// DELETE api/values/5 | |||||
[HttpDelete("{id}")] | |||||
public void Delete(Guid id) | |||||
{ | |||||
_repository.DeleteBasket(id); | |||||
} | |||||
} | |||||
} |
@ -1,44 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers | |||||
{ | |||||
[Route("api/[controller]")] | |||||
public class ValuesController : Controller | |||||
{ | |||||
// GET api/values | |||||
[HttpGet] | |||||
public IEnumerable<string> Get() | |||||
{ | |||||
return new string[] { "value1", "value2" }; | |||||
} | |||||
// GET api/values/5 | |||||
[HttpGet("{id}")] | |||||
public string Get(int id) | |||||
{ | |||||
return "value"; | |||||
} | |||||
// POST api/values | |||||
[HttpPost] | |||||
public void Post([FromBody]string value) | |||||
{ | |||||
} | |||||
// PUT api/values/5 | |||||
[HttpPut("{id}")] | |||||
public void Put(int id, [FromBody]string value) | |||||
{ | |||||
} | |||||
// DELETE api/values/5 | |||||
[HttpDelete("{id}")] | |||||
public void Delete(int id) | |||||
{ | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,6 @@ | |||||
FROM microsoft/aspnetcore:1.0.1 | |||||
ENTRYPOINT ["dotnet", "Basket.API.dll"] | |||||
ARG source=. | |||||
WORKDIR /app | |||||
EXPOSE 80 | |||||
COPY $source . |
@ -0,0 +1,18 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model | |||||
{ | |||||
public class CustomerBasket | |||||
{ | |||||
public Guid CustomerId { get; private set; } | |||||
public IList<BasketItem> BasketItems => new List<BasketItem>(); | |||||
public CustomerBasket(Guid customerId) | |||||
{ | |||||
CustomerId = customerId; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,14 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model | |||||
{ | |||||
public class BasketItem | |||||
{ | |||||
public Guid Id { get; set; } | |||||
public decimal Price { get; set; } | |||||
public int Count { get; set; } | |||||
} | |||||
} |
@ -0,0 +1,14 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model | |||||
{ | |||||
public interface IBasketRepository | |||||
{ | |||||
Task<CustomerBasket> GetBasket(Guid customerId); | |||||
Task<bool> UpdateBasket(CustomerBasket basket); | |||||
Task<bool> DeleteBasket(Guid id); | |||||
} | |||||
} |
@ -0,0 +1,66 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using StackExchange.Redis; | |||||
using Microsoft.Extensions.Options; | |||||
using Newtonsoft.Json; | |||||
using Microsoft.Extensions.Logging; | |||||
using System.Net; | |||||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model | |||||
{ | |||||
public class RedisBasketRepository : IBasketRepository | |||||
{ | |||||
private ILogger<RedisBasketRepository> _logger; | |||||
private BasketSettings _settings; | |||||
private ConnectionMultiplexer _redis; | |||||
public RedisBasketRepository(IOptions<BasketSettings> options, ILoggerFactory loggerFactory) | |||||
{ | |||||
_settings = options.Value; | |||||
_logger = loggerFactory.CreateLogger<RedisBasketRepository>(); | |||||
} | |||||
public async Task<bool> DeleteBasket(Guid id) | |||||
{ | |||||
var database = await GetDatabase(); | |||||
return await database.KeyDeleteAsync(id.ToString()); | |||||
} | |||||
public async Task<CustomerBasket> GetBasket(Guid customerId) | |||||
{ | |||||
var database = await GetDatabase(); | |||||
var data = await database.StringGetAsync(customerId.ToString()); | |||||
if (data.IsNullOrEmpty) | |||||
{ | |||||
return null; | |||||
} | |||||
return JsonConvert.DeserializeObject<CustomerBasket>(data); | |||||
} | |||||
public async Task<bool> UpdateBasket(CustomerBasket basket) | |||||
{ | |||||
var database = await GetDatabase(); | |||||
return await database.StringSetAsync(basket.CustomerId.ToString(), JsonConvert.SerializeObject(basket)); | |||||
} | |||||
private async Task<IDatabase> GetDatabase() | |||||
{ | |||||
if (_redis == null) | |||||
{ | |||||
//TODO: Need to make this more robust. Also want to understand why the static connection method cannot accept dns names. | |||||
var ips = await Dns.GetHostAddressesAsync(_settings.ConnectionString); | |||||
_logger.LogInformation($"Connecting to database {_settings.ConnectionString} at IP {ips.First().ToString()}"); | |||||
_redis = await ConnectionMultiplexer.ConnectAsync(ips.First().ToString()); | |||||
} | |||||
return _redis.GetDatabase(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,17 @@ | |||||
version: '2' | |||||
services: | |||||
basket.api: | |||||
build: | |||||
args: | |||||
source: obj/Docker/empty/ | |||||
labels: | |||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux" | |||||
environment: | |||||
- ASPNETCORE_ENVIRONMENT=Development | |||||
- DOTNET_USE_POLLING_FILE_WATCHER=1 | |||||
volumes: | |||||
- .:/app | |||||
- ~/.nuget/packages:/root/.nuget/packages:ro | |||||
- ~/clrdbg:/clrdbg:ro | |||||
entrypoint: tail -f /dev/null |
@ -0,0 +1,9 @@ | |||||
version: '2' | |||||
services: | |||||
basket.api: | |||||
labels: | |||||
- "com.microsoft.visualstudio.targetoperatingsystem=linux" | |||||
volumes: | |||||
- ~/clrdbg:/clrdbg:ro | |||||
entrypoint: tail -f /dev/null |
@ -0,0 +1,16 @@ | |||||
version: '2' | |||||
services: | |||||
basket.api: | |||||
image: user/basket.api${TAG} | |||||
environment: | |||||
- ConnectionString=basket.data | |||||
build: | |||||
context: . | |||||
dockerfile: Dockerfile | |||||
ports: | |||||
- "32783:80" | |||||
depends_on: | |||||
- basket.data | |||||
basket.data: | |||||
image: redis |