Added some wrapper around the http context so i can at least pretend to access things from it without casting them

This commit is contained in:
TomPallister 2016-10-05 20:46:00 +01:00
parent ab8407e7dc
commit 229c0878ed
9 changed files with 208 additions and 32 deletions

View File

@ -0,0 +1,11 @@
using Ocelot.Library.Infrastructure.Responses;
namespace Ocelot.Library.Infrastructure.Services
{
public class CannotAddDataError : Error
{
public CannotAddDataError(string message) : base(message)
{
}
}
}

View File

@ -0,0 +1,11 @@
using Ocelot.Library.Infrastructure.Responses;
namespace Ocelot.Library.Infrastructure.Services
{
public class CannotFindDataError : Error
{
public CannotFindDataError(string message) : base(message)
{
}
}
}

View File

@ -0,0 +1,10 @@
using Ocelot.Library.Infrastructure.Responses;
namespace Ocelot.Library.Infrastructure.Services
{
public interface IRequestDataService
{
Response Add<T>(string key, T value);
Response<T> Get<T>(string key);
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Ocelot.Library.Infrastructure.Responses;
namespace Ocelot.Library.Infrastructure.Services
{
public class RequestDataService : IRequestDataService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public RequestDataService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Response Add<T>(string key, T value)
{
try
{
_httpContextAccessor.HttpContext.Items.Add(key, value);
return new OkResponse();
}
catch (Exception exception)
{
return new ErrorResponse(new List<Error>
{
new CannotAddDataError(string.Format($"Unable to add data for key: {key}, exception: {exception.Message}"))
});
}
}
public Response<T> Get<T>(string key)
{
object obj;
if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj))
{
var data = (T) obj;
return new OkResponse<T>(data);
}
return new ErrorResponse<T>(new List<Error>
{
new CannotFindDataError($"Unable to find data for key: {key}")
});
}
}
}

View File

@ -2,6 +2,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Infrastructure.Responder; using Ocelot.Library.Infrastructure.Responder;
using Ocelot.Library.Infrastructure.Services;
namespace Ocelot.Library.Middleware namespace Ocelot.Library.Middleware
{ {
@ -10,14 +11,17 @@ namespace Ocelot.Library.Middleware
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IDownstreamRouteFinder _downstreamRouteFinder; private readonly IDownstreamRouteFinder _downstreamRouteFinder;
private readonly IHttpResponder _responder; private readonly IHttpResponder _responder;
private readonly IRequestDataService _requestDataService;
public DownstreamRouteFinderMiddleware(RequestDelegate next, public DownstreamRouteFinderMiddleware(RequestDelegate next,
IDownstreamRouteFinder downstreamRouteFinder, IDownstreamRouteFinder downstreamRouteFinder,
IHttpResponder responder) IHttpResponder responder,
IRequestDataService requestDataService)
{ {
_next = next; _next = next;
_downstreamRouteFinder = downstreamRouteFinder; _downstreamRouteFinder = downstreamRouteFinder;
_responder = responder; _responder = responder;
_requestDataService = requestDataService;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
@ -32,7 +36,7 @@ namespace Ocelot.Library.Middleware
return; return;
} }
context.Items.Add("DownstreamRoute", downstreamRoute.Data); _requestDataService.Add("DownstreamRoute", downstreamRoute.Data);
await _next.Invoke(context); await _next.Invoke(context);
} }

View File

@ -1,6 +1,8 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Infrastructure.Responder;
using Ocelot.Library.Infrastructure.Services;
using Ocelot.Library.Infrastructure.UrlTemplateReplacer; using Ocelot.Library.Infrastructure.UrlTemplateReplacer;
namespace Ocelot.Library.Middleware namespace Ocelot.Library.Middleware
@ -9,34 +11,35 @@ namespace Ocelot.Library.Middleware
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IDownstreamUrlTemplateVariableReplacer _urlReplacer; private readonly IDownstreamUrlTemplateVariableReplacer _urlReplacer;
private readonly IRequestDataService _requestDataService;
private readonly IHttpResponder _responder;
public DownstreamUrlCreatorMiddleware(RequestDelegate next, public DownstreamUrlCreatorMiddleware(RequestDelegate next,
IDownstreamUrlTemplateVariableReplacer urlReplacer) IDownstreamUrlTemplateVariableReplacer urlReplacer,
IRequestDataService requestDataService,
IHttpResponder responder)
{ {
_next = next; _next = next;
_urlReplacer = urlReplacer; _urlReplacer = urlReplacer;
_requestDataService = requestDataService;
_responder = responder;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var downstreamRoute = GetDownstreamRouteFromOwinItems(context); var downstreamRoute = _requestDataService.Get<DownstreamRoute>("DownstreamRoute");
var downstreamUrl = _urlReplacer.ReplaceTemplateVariables(downstreamRoute); if (downstreamRoute.IsError)
{
await _responder.CreateNotFoundResponse(context);
return;
}
context.Items.Add("DownstreamUrl", downstreamUrl); var downstreamUrl = _urlReplacer.ReplaceTemplateVariables(downstreamRoute.Data);
_requestDataService.Add("DownstreamUrl", downstreamUrl);
await _next.Invoke(context); await _next.Invoke(context);
} }
private DownstreamRoute GetDownstreamRouteFromOwinItems(HttpContext context)
{
object obj;
DownstreamRoute downstreamRoute = null;
if (context.Items.TryGetValue("DownstreamRoute", out obj))
{
downstreamRoute = (DownstreamRoute) obj;
}
return downstreamRoute;
}
} }
} }

View File

@ -2,6 +2,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Library.Infrastructure.Requester; using Ocelot.Library.Infrastructure.Requester;
using Ocelot.Library.Infrastructure.Responder; using Ocelot.Library.Infrastructure.Responder;
using Ocelot.Library.Infrastructure.Services;
namespace Ocelot.Library.Middleware namespace Ocelot.Library.Middleware
{ {
@ -10,38 +11,36 @@ namespace Ocelot.Library.Middleware
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IHttpRequester _requester; private readonly IHttpRequester _requester;
private readonly IHttpResponder _responder; private readonly IHttpResponder _responder;
private readonly IRequestDataService _requestDataService;
public HttpRequesterMiddleware(RequestDelegate next, public HttpRequesterMiddleware(RequestDelegate next,
IHttpRequester requester, IHttpRequester requester,
IHttpResponder responder) IHttpResponder responder,
IRequestDataService requestDataService)
{ {
_next = next; _next = next;
_requester = requester; _requester = requester;
_responder = responder; _responder = responder;
_requestDataService = requestDataService;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var downstreamUrl = GetDownstreamUrlFromOwinItems(context); var downstreamUrl = _requestDataService.Get<string>("DownstreamUrl");
if (downstreamUrl.IsError)
{
await _responder.CreateNotFoundResponse(context);
return;
}
var response = await _requester var response = await _requester
.GetResponse(context.Request.Method, downstreamUrl, context.Request.Body, .GetResponse(context.Request.Method, downstreamUrl.Data, context.Request.Body,
context.Request.Headers, context.Request.Cookies, context.Request.Query, context.Request.ContentType); context.Request.Headers, context.Request.Cookies, context.Request.Query, context.Request.ContentType);
await _responder.CreateResponse(context, response); await _responder.CreateResponse(context, response);
await _next.Invoke(context); await _next.Invoke(context);
} }
private string GetDownstreamUrlFromOwinItems(HttpContext context)
{
object obj;
string downstreamUrl = null;
if (context.Items.TryGetValue("DownstreamUrl", out obj))
{
downstreamUrl = (string) obj;
}
return downstreamUrl;
}
} }
} }

View File

@ -1,11 +1,13 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Infrastructure.Requester; using Ocelot.Library.Infrastructure.Requester;
using Ocelot.Library.Infrastructure.Responder; using Ocelot.Library.Infrastructure.Responder;
using Ocelot.Library.Infrastructure.Services;
using Ocelot.Library.Middleware; using Ocelot.Library.Middleware;
namespace Ocelot namespace Ocelot
@ -42,6 +44,10 @@ namespace Ocelot
services.AddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder>(); services.AddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder>();
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>(); services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
services.AddSingleton<IHttpResponder, HttpContextResponder>(); services.AddSingleton<IHttpResponder, HttpContextResponder>();
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IRequestDataService, RequestDataService>();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Moq;
using Ocelot.Library.Infrastructure.Responses;
using Ocelot.Library.Infrastructure.Services;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Services
{
public class RequestDataServiceTests
{
private IRequestDataService _requestDataService;
private IHttpContextAccessor _httpContextAccesor;
private string _key;
private object _toAdd;
private Response<int[]> _result;
public RequestDataServiceTests()
{
_httpContextAccesor = new HttpContextAccessor();
_httpContextAccesor.HttpContext = new DefaultHttpContext();
_requestDataService = new RequestDataService(_httpContextAccesor);
}
[Fact]
public void should_add_item()
{
this.Given(x => x.GivenIHaveAnItemToAdd("blahh", new [] {1,2,3,4}))
.When(x => x.WhenIAddTheItem())
.Then(x => x.ThenTheItemIsAdded())
.BDDfy();
}
[Fact]
public void should_get_item()
{
this.Given(x => x.GivenThereIsAnItemInTheContext("chest"))
.When(x => x.WhenIGetTheItem())
.Then(x => x.ThenTheItemIsReturned())
.BDDfy();
}
private void ThenTheItemIsReturned()
{
_result.IsError.ShouldBeFalse();
_result.Data.ShouldNotBeNull();
}
private void WhenIGetTheItem()
{
_result = _requestDataService.Get<int[]>(_key);
}
private void GivenThereIsAnItemInTheContext(string key)
{
_key = key;
var data = new[] {5435345};
_httpContextAccesor.HttpContext.Items.Add(key, data);
}
private void GivenIHaveAnItemToAdd(string key, object toAdd)
{
_key = key;
_toAdd = toAdd;
}
private void WhenIAddTheItem()
{
_requestDataService.Add(_key, _toAdd);
}
private void ThenTheItemIsAdded()
{
object obj;
_httpContextAccesor.HttpContext.Items.TryGetValue(_key, out obj).ShouldBeTrue();
}
}
}