Mock the business layer for integration testing the Web API - asp.net-web-api

I want to do integration tests on my Web API without depending on my business layer interfaces.
When this action is run:
1) I want to mock the _service object and just verify that is is called
2) I want to assert that the correct StatusCode is returned
Number 2 is no problem but how can I mock the _service object (ISchoolyearService) when I do not control/start the creation of the api controller manually because this is a task done in unit testing the controller. But I do not want to unit test my API !
[RoutePrefix("api/schoolyears")]
public class SchoolyearController : ApiController
{
private readonly ISchoolyearService _service;
public SchoolyearController(ISchoolyearService service)
{
_service = service;
}
[Route("")]
[HttpPost]
public HttpResponseMessage Post([FromBody]SchoolyearCreateRequest request)
{
_service.CreateSchoolyear(request);
return Request.CreateResponse(HttpStatusCode.Created);
}

Following is a crude example of how you can do with in-memory integration testing. Here I am using Unity.WebApi.UnityDependencyResolver to inject mock dependencies. You can use any other IoC container similarly.
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using Unity.WebApi;
namespace WebApplication251.Tests.Controllers
{
[TestClass]
public class PeopleControllerTest
{
string baseAddress = "http://dummyhost/";
[TestMethod]
public void PostTest()
{
HttpConfiguration config = new HttpConfiguration();
// use the configuration that the web application has defined
WebApiConfig.Register(config);
//override the dependencies with mock ones
RegisterMockDependencies(config);
HttpServer server = new HttpServer(config);
//create a client with a handler which makes sure to exercise the formatters
HttpClient client = new HttpClient(new InMemoryHttpContentSerializationHandler(server));
SchoolyearCreateRequest req = new SchoolyearCreateRequest();
using (HttpResponseMessage response = client.PostAsJsonAsync<SchoolyearCreateRequest>(baseAddress + "api/schoolyears", req).Result)
{
Assert.IsNotNull(response.Content);
Assert.IsNotNull(response.Content.Headers.ContentType);
Assert.AreEqual<string>("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
SchoolyearCreateRequest recSCR = response.Content.ReadAsAsync<SchoolyearCreateRequest>().Result;
//todo: verify data
}
}
private void RegisterMockDependencies(HttpConfiguration config)
{
var unity = new UnityContainer();
unity.RegisterType<ISchoolyearService, MockSchoolyearService>();
config.DependencyResolver = new UnityDependencyResolver(unity);
}
}
[RoutePrefix("api/schoolyears")]
public class SchoolyearController : ApiController
{
private readonly ISchoolyearService _service;
public SchoolyearController(ISchoolyearService service)
{
_service = service;
}
[Route]
[HttpPost]
public HttpResponseMessage Post([FromBody]SchoolyearCreateRequest request)
{
_service.CreateSchoolyear(request);
return Request.CreateResponse(HttpStatusCode.Created);
}
}
public class InMemoryHttpContentSerializationHandler : DelegatingHandler
{
public InMemoryHttpContentSerializationHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Content = await ConvertToStreamContentAsync(request.Content);
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
response.Content = await ConvertToStreamContentAsync(response.Content);
return response;
}
private async Task<StreamContent> ConvertToStreamContentAsync(HttpContent originalContent)
{
if (originalContent == null)
{
return null;
}
StreamContent streamContent = originalContent as StreamContent;
if (streamContent != null)
{
return streamContent;
}
MemoryStream ms = new MemoryStream();
await originalContent.CopyToAsync(ms);
// Reset the stream position back to 0 as in the previous CopyToAsync() call,
// a formatter for example, could have made the position to be at the end
ms.Position = 0;
streamContent = new StreamContent(ms);
// copy headers from the original content
foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
{
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return streamContent;
}
}
}

Related

Can API Key and JWT Token be used in the same .Net 6 WebAPI

I am building a new .Net 6 WebAPI that will be consumed by many applications so I need to implement API Keys to limit access to only those applications. Only a very small amount of the individual users will require authorization (admins) so I would like to combine with JWT for the Admin endpoints. We do not want to require users to have to crate an account where not necessary (non-admins). Is this possible? Thank You.
Yes it is possible.
The solution I recommend is to setup multiple authentication methods in asp.net core 6 using two authentication schemes that you have to specify inside Authorize attribute.
Here a simple implementation of ApiKey authentication:
namespace MyAuthentication;
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
private enum AuthenticationFailureReason
{
NONE = 0,
API_KEY_HEADER_NOT_PROVIDED,
API_KEY_HEADER_VALUE_NULL,
API_KEY_INVALID
}
private readonly Microsoft.Extensions.Logging.ILogger _logger;
private AuthenticationFailureReason _failureReason = AuthenticationFailureReason.NONE;
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory loggerFactory,
ILogger<ApiKeyAuthenticationHandler> logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, loggerFactory, encoder, clock)
{
_logger = logger;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//ApiKey header get
if (!TryGetApiKeyHeader(out string providedApiKey, out AuthenticateResult authenticateResult))
{
return authenticateResult;
}
//TODO: you apikey validity check
if (await ApiKeyCheckAsync(providedApiKey))
{
var principal = new ClaimsPrincipal(); //TODO: Create your Identity retreiving claims
var ticket = new AuthenticationTicket(principal, ApiKeyAuthenticationOptions.Scheme);
return AuthenticateResult.Success(ticket);
}
_failureReason = AuthenticationFailureReason.API_KEY_INVALID;
return AuthenticateResult.NoResult();
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
//Create response
Response.Headers.Append(HeaderNames.WWWAuthenticate, $#"Authorization realm=""{ApiKeyAuthenticationOptions.DefaultScheme}""");
Response.StatusCode = StatusCodes.Status401Unauthorized;
Response.ContentType = MediaTypeNames.Application.Json;
//TODO: setup a response to provide additional information if you want
var result = new
{
StatusCode = Response.StatusCode,
Message = _failureReason switch
{
AuthenticationFailureReason.API_KEY_HEADER_NOT_PROVIDED => "ApiKey not provided",
AuthenticationFailureReason.API_KEY_HEADER_VALUE_NULL => "ApiKey value is null",
AuthenticationFailureReason.NONE or AuthenticationFailureReason.API_KEY_INVALID or _ => "ApiKey is not valid"
}
};
using var responseStream = new MemoryStream();
await JsonSerializer.SerializeAsync(responseStream, result);
await Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
{
//Create response
Response.Headers.Append(HeaderNames.WWWAuthenticate, $#"Authorization realm=""{ApiKeyAuthenticationOptions.DefaultScheme}""");
Response.StatusCode = StatusCodes.Status403Forbidden;
Response.ContentType = MediaTypeNames.Application.Json;
var result = new
{
StatusCode = Response.StatusCode,
Message = "Forbidden"
};
using var responseStream = new MemoryStream();
await JsonSerializer.SerializeAsync(responseStream, result);
await Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
#region Privates
private bool TryGetApiKeyHeader(out string apiKeyHeaderValue, out AuthenticateResult result)
{
apiKeyHeaderValue = null;
if (!Request.Headers.TryGetValue("X-Api-Key", out var apiKeyHeaderValues))
{
_logger.LogError("ApiKey header not provided");
_failureReason = AuthenticationFailureReason.API_KEY_HEADER_NOT_PROVIDED;
result = AuthenticateResult.Fail("ApiKey header not provided");
return false;
}
apiKeyHeaderValue = apiKeyHeaderValues.FirstOrDefault();
if (apiKeyHeaderValues.Count == 0 || string.IsNullOrWhiteSpace(apiKeyHeaderValue))
{
_logger.LogError("ApiKey header value null");
_failureReason = AuthenticationFailureReason.API_KEY_HEADER_VALUE_NULL;
result = AuthenticateResult.Fail("ApiKey header value null");
return false;
}
result = null;
return true;
}
private Task<bool> ApiKeyCheckAsync(string apiKey)
{
//TODO: setup your validation code...
return Task.FromResult<bool>(true);
}
#endregion
}
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "ApiKey";
public static string Scheme => DefaultScheme;
public static string AuthenticationType => DefaultScheme;
}
public static class AuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddApiKeySupport(this AuthenticationBuilder authenticationBuilder, Action<ApiKeyAuthenticationOptions> options)
=> authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationOptions.DefaultScheme, options);
}
Then register inside builder setup:
_ = services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = ApiKeyAuthenticationOptions.DefaultScheme;
options.DefaultChallengeScheme = ApiKeyAuthenticationOptions.DefaultScheme;
})
.AddApiKeySupport(options => { });
You have to also setup the standard JWT Bearer validation (I don't post it for the sake of brevity).
To protect your endpoint add the Authorize attribute like:
[Authorize(AuthenticationSchemes = ApiKeyAuthenticationOptions.DefaultScheme)] //ApiKey
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
//or..
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] //Jwt
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
//or..
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme},{ApiKeyAuthenticationOptions.DefaultScheme}" )] //ApiKey and Jwt
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
For me it is the best way so as to carry out the authorization check before the start of the application pipeline (fail fast) and to be able to create the user identity.
But if you don't need to put informations about the Api Key inside the ClaimsPrincipal and only check the validity of Api Key the simplest way to do that is:
Protect the "admin" actions with JWT auth (with Authorize attribute)
Setup and register a middleware to only check the Api Key in all actions
Here is an example:
public class SimpleApiKeyMiddleware
{
private static readonly string API_KEY_HEADER = "X-Api-Key";
private readonly RequestDelegate _next;
private readonly ILogger<SimpleApiKeyMiddleware> _logger;
public SimpleApiKeyMiddleware(RequestDelegate next, ILogger<SimpleApiKeyMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext httpContext)
{
//Get apikey header
if (!httpContext.Request.Headers.TryGetValue(API_KEY_HEADER, out var apiKey))
{
_logger.LogError("ApiKey not found inside request headers");
//Error and exit from asp.net core pipeline
await GenerateForbiddenResponse(httpContext, "ApiKey not found inside request headers");
}
else if (!await ApiKeyCheckAsync(apiKey))
{
_logger.LogError("ApiKey is not valid: {ApiKey}", apiKey);
//Error and exit from asp.net core pipeline
await GenerateForbiddenResponse(httpContext, "ApiKey not valid");
}
else
{
_logger.LogInformation("ApiKey validated: {ApiKey}", apiKey);
//Proceed with pipeline
await _next(httpContext);
}
}
private Task<bool> ApiKeyCheckAsync(string apiKey)
{
//TODO: setup your validation code...
return Task.FromResult<bool>(true);
}
private async Task GenerateForbiddenResponse(HttpContext context, string message)
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
context.Response.ContentType = MediaTypeNames.Application.Json;
using var responseStream = new MemoryStream();
await System.Text.Json.JsonSerializer.SerializeAsync(responseStream, new
{
Status = StatusCodes.Status403Forbidden,
Message = message
});
await context.Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
}
Registration:
_ = app.UseMiddleware<ApiKeyMiddleware>(); //Register as first middleware to avoid other middleware execution before api key check
Usage:
//Admin: Jwt and Api Key check
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] //Jwt and Api Key
[HttpGet]
public async Task<IActionResult> MyAdminApi()
{
//...omissis...
}
//Non Admin: Api Key check only
[HttpGet]
public async Task<IActionResult> MyNonAdminApi()
{
//...omissis...
}
Note: the middleware code above forces exit from pipeline returning an http result so as to stop next middleware execution. Also note that the asp.net core 6 pipeline executes Authorization first and then all the registered middlewares.

Asp.net-core api response WaitingForActivision

Can someone point me into the right direction what I am doing wrong in this Api call? I am getting an odd error that I don’t know what it means. The api call should work as I tested it using VBA and I get a response with the payload. Also any feedback is welcomed.
Id = 190, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}" - this is the response I am getting back from it
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Xml.Linq;
namespace BarcodeScanner.Classes
{
public class Api : IDisposable
{
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private readonly string _credentials;
private const string MediaTypeXml = "application/xml";
public Api(string baseUrl, string authToken, TimeSpan? timeout = null)
{
_baseUrl = NormaliseBaseUrl(baseUrl);
_credentials = Base64Encode(authToken);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task<string> GetAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.GetAsync(url).ConfigureAwait(continueOnCapturedContext: false))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient()
{
_httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false)
{
Timeout = _timeout
};
if (!string.IsNullOrWhiteSpace(_baseUrl))
{
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + _credentials);
}
private void EnsureHttpClientCreated()
{
if (_httpClient == null)
{
CreateHttpClient();
}
}
//call the api
try
{
using (var client = new Api(requestUrl, authToken))
{
var response = client.GetAsync(requestUrl);
}
}
catch (Exception err)
{
throw new Exception("Something went wrong: ", err);
}

GraphQL is returning extra information which is slowing down the initial loading

I am getting some extra information within my graphql results.
Apart from the data and the end errors I am getting
document
operation
perf
extensions
so the result is getting quite bulky. The other think I've noticed is that the initial loading of the documents and the intellisens are taking ages to load.
Any idea how I can get rid of this additional data?
Result of the graphQL query:
GraphQL Controller
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ElectronConnectQuery.GraphQL;
using GraphQL;
using GraphQL.DataLoader;
using GraphQL.NewtonsoftJson;
using GraphQL.Types;
using GraphQL.Validation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace ElectronConnectQuery.Controllers.v1
{
[Route("[controller]")]
public class GraphQLController : Controller
{
private readonly IDocumentExecuter _documentExecuter;
private readonly ISchema _schema;
private readonly DataLoaderDocumentListener _listener;
private readonly ILogger<GraphQLController> _logger;
public GraphQLController(ISchema schema, IDocumentExecuter documentExecuter, DataLoaderDocumentListener listener, ILogger<GraphQLController> logger)
{
_schema = schema;
_documentExecuter = documentExecuter;
_listener = listener;
_logger = logger;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] GraphQLQuery query, [FromServices] IEnumerable<IValidationRule> validationRules)
{
if (query == null) { throw new ArgumentNullException(nameof(query)); }
_logger.LogDebug("GraphQL received query:{Query}", query.Query);
var inputs = query.Variables.ToInputs();
var executionOptions = new ExecutionOptions
{
Schema = _schema,
Query = query.Query,
Inputs = inputs,
ValidationRules = validationRules,
EnableMetrics = false
};
#if (DEBUG)
executionOptions.EnableMetrics = true;
#endif
executionOptions.Listeners.Add(_listener);
var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);
if (result.Errors?.Count > 0)
{
return BadRequest(result);
}
return Ok(result);
}
}
}
Instead if writing the result yourself, use the IDocumentWriter, which will properly serialize the result.
/// <summary>
/// Serializes an object hierarchy to a stream. Typically this would be serializing an instance of the ExecutionResult class into a JSON stream.
/// </summary>
public interface IDocumentWriter
{
/// <summary>
/// Asynchronously serializes the specified object to the specified stream.
/// </summary>
Task WriteAsync<T>(Stream stream, T value, CancellationToken cancellationToken = default);
}
There is also an extension method to serialize to a string.
public static async Task<string> WriteToStringAsync<T>(this IDocumentWriter writer, T value)
This example shows using middleware vs. a controller but the idea is the same.
https://github.com/graphql-dotnet/examples/blob/529b530d7a6aad878b2757d776282fdc1cdcb595/src/AspNetCoreCustom/Example/GraphQLMiddleware.cs#L75-L81
private async Task WriteResponseAsync(HttpContext context, ExecutionResult result)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = result.Errors?.Any() == true ? (int)HttpStatusCode.BadRequest : (int)HttpStatusCode.OK;
await _writer.WriteAsync(context.Response.Body, result);
}
You will need to include GraphQL.SystemTextJson or GraphQL.NewtonSoftJson to choose your implementation of IDocumentWriter.
https://www.nuget.org/packages/GraphQL.SystemTextJson
https://www.nuget.org/packages/GraphQL.NewtonsoftJson
The change which I've done to the controller is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using ElectronConnectQuery.GraphQL;
using GraphQL;
using GraphQL.DataLoader;
using GraphQL.Instrumentation;
using GraphQL.NewtonsoftJson;
using GraphQL.Types;
using GraphQL.Validation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace ElectronConnectQuery.Controllers.v1
{
[Route("[controller]")]
public class GraphQLController : Controller
{
private readonly IDocumentExecuter _documentExecuter;
private readonly ISchema _schema;
private readonly DataLoaderDocumentListener _listener;
private readonly ILogger<GraphQLController> _logger;
private readonly IDocumentWriter _writer;
public GraphQLController(ISchema schema, IDocumentExecuter documentExecuter, DataLoaderDocumentListener listener, ILogger<GraphQLController> logger, IDocumentWriter writer)
{
_schema = schema;
_documentExecuter = documentExecuter;
_listener = listener;
_logger = logger;
_writer = writer;
}
[HttpPost]
public async Task Post([FromBody] GraphQLQuery query, [FromServices] IEnumerable<IValidationRule> validationRules)
{
if (query == null) { throw new ArgumentNullException(nameof(query)); }
_logger.LogDebug("GraphQL received query:{Query}", query.Query);
var inputs = query.Variables.ToInputs();
var executionOptions = new ExecutionOptions
{
Schema = _schema,
Query = query.Query,
Inputs = inputs,
ValidationRules = validationRules,
EnableMetrics = false,
};
executionOptions.Listeners.Add(_listener);
var result = await _documentExecuter.ExecuteAsync(opts =>
{
opts.Schema = _schema;
opts.Query = query.Query;
opts.Inputs = inputs;
opts.ValidationRules = validationRules;
opts.FieldMiddleware.Use<InstrumentFieldsMiddleware>();
opts.EnableMetrics = true;
}).ConfigureAwait(false);
result.EnrichWithApolloTracing(DateTime.Now);
await _writer.WriteAsync(Response.Body, result);
}
private async Task WriteResponseAsync(HttpResponse response, ExecutionResult result)
{
response.ContentType = "application/json";
response.StatusCode = result.Errors?.Any() == true ? (int)HttpStatusCode.BadRequest : (int)HttpStatusCode.OK;
await _writer.WriteAsync(response.Body, result);
}
}
}
Startup.cs
In ConfigureServices
I have added the following lines
// kestrel
services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
// IIS
services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
Also I've registered the DocumentWriter
services.AddScoped<IDocumentWriter, GraphQL.NewtonsoftJson.DocumentWriter>();

Update cache upon modified entity

I am using IMemoryCache and running an asp-net core project. On the home page I have listed some movies, which are cached for like 10 minutes. Is there a way to update the cache, If a movie has been created/deleted/edit, If if those 10 minutes have not passed?
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using MovieManagement.Models;
using MovieManagement.Models.Home;
using MovieManagement.Services.Contracts;
using MovieManagement.ViewModels;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace MovieManagement.Controllers
{
public class HomeController : Controller
{
private readonly IMovieService movieService;
private readonly IMemoryCache cacheService;
public HomeController(IMovieService movieService, IMemoryCache cache)
{
this.movieService = movieService ?? throw new ArgumentNullException(nameof(movieService));
this.cacheService = cache ?? throw new ArgumentNullException(nameof(cache));
}
public async Task<IActionResult> Index()
{
var model = new HomeIndexViewModel();
var cachedMovies = await this.cacheService.GetOrCreateAsync("Movies", async entry =>
{
entry.AbsoluteExpiration = DateTime.UtcNow.AddSeconds(20);
var movies = await this.movieService.GetTopRatedMovies();
return movies;
});
model.Movies = cachedMovies;
return this.View(model);
}
}
}
You could update the cached values on Delete/Create/Edit via a shared private method:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using MovieManagement.Models;
using MovieManagement.Models.Home;
using MovieManagement.Services.Contracts;
using MovieManagement.ViewModels;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace MovieManagement.Controllers
{
public class HomeController : Controller
{
private readonly IMovieService movieService;
private readonly IMemoryCache cacheService;
public HomeController(IMovieService movieService, IMemoryCache cache)
{
this.movieService = movieService ?? throw new ArgumentNullException(nameof(movieService));
this.cacheService = cache ?? throw new ArgumentNullException(nameof(cache));
}
public async Task<IActionResult> Index()
{
var model = new HomeIndexViewModel();
var cachedMovies = await this.cacheService.GetOrCreateAsync("Movies", async entry =>
{
entry.AbsoluteExpiration = DateTime.UtcNow.AddMinutes(10);
var movies = await this.movieService.GetTopRatedMovies();
return movies;
});
model.Movies = cachedMovies;
return this.View(model);
}
[HttpPost]
public async Task<IActionResult> Delete(int id)
{
this.movieService.Delete(id);
UpdateCachedMovies();
return RedirectToAction(nameof(Index));
}
[HttpPost]
public async Task<IActionResult> Create(Movie model)
{
this.movieService.Add(model);
UpdateCachedMovies();
return RedirectToAction(nameof(Index));
}
private async void UpdateCachedMovies()
{
this.cacheService.Set("Movies", this.movieService.GetTopRatedMovies(), DateTime.UtcNow.AddMinutes(10));
}
}
}

How to host web api in memory?

I have web api controller and i want to perform an integration tests. So I followed the article here to configured the in-memory Web API host.
My integration test and web api are two different projects in same VS solution.
Below is the code
Web API Controller
public class DocumentController : ApiController
{
public DocumentController(IDomainService domainService)
{
_domainService = domainService;
}
[HttpPost]
public async Task<IEnumerable<string>> Update([FromBody]IEnumerable<Document> request)
{
return await _domainService.Update(request).ConfigureAwait(false);
}
}
Integration Test
[TestClass]
public class IntegrationTests
{
private HttpServer _server;
private string _url = "http://www.strathweb.com/";
[TestInitialize]
public void Init()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(name: "Default", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.MessageHandlers.Add()
_server = new HttpServer(config);
}
[TestMethod]
public void UpdateTransformedDocuments()
{
var doc = new Document()
{
// set all properties
}
var client = new HttpClient(_server);
var request = createRequest<Document>("api/document/Update", "application/json", HttpMethod.Post, doc, new JsonMediaTypeFormatter());
using (var response = client.SendAsync(request).Result)
{
// do something with response here
}
}
private HttpRequestMessage createRequest<T>(string url, string mthv, HttpMethod method, T content, MediaTypeFormatter formatter) where T : class
{
Create HttpRequestMessage here
}
}
However im getting error
StatusCode: 404, ReasonPhrase: 'Not Found'
How & where do I tell the HttpServer to execute DocumentController?
Update1
So I fixed above error by changing the [TestIntialize] code as below
[TestInitialize]
public void Init()
{
var config = new HttpConfiguration();
UnityWebApiActivator.Start();
WebApiConfig.Register(config);
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
_server = new HttpServer(config);
}
I don't get 404 error now. However Unity is not able to resolve DocumentController. The HttpResponse contains error
An error occurred when trying to create a controller of type
'DocumentController'. Make sure that the controller has a
parameterless public constructor.
In TestInitialize method I'm calling UnityWebApiActivator.Start() which registers all the require types with Unity.
i resolved my 2nd issue by setting 'HttpConfiguration.DependencyResolver'
[TestInitialize]
public void Init()
{
var config = new HttpConfiguration();
//UnityWebApiActivator.Start();
config.DependencyResolver = new UnityHierarchicalDependencyResolver(UnityConfig.GetConfiguredContainer());
WebApiConfig.Register(config);
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
_server = new HttpServer(config);
}

Resources