How to host web api in memory? - asp.net-web-api

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);
}

Related

Autofac DI wiring up issue in webapi asp.net project

Having an issue getting a service instance in my controller. Followed the documentation from autofac's website but still getting an error. "None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'MyAssembly.Controllers.HeartBeatController' can be invoked with the available services and parameters:\r\nCannot resolve parameter 'MyAssembly.IO.IConfig config' of constructor 'Void .ctor(MyAssembly.IO.IConfig)'."
Here is my Global.asax file
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var builder = new ContainerBuilder();
var config = GlobalConfiguration.Configuration;
WebApiConfig.Register(config);
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
var container = builder.Build();
BuildServices(builder);
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
config.EnsureInitialized();
log4net.Config.XmlConfigurator.Configure(new FileInfo(Server.MapPath("~/Web.config")));
}
private void BuildServices(ContainerBuilder builder)
{
builder.RegisterType<Config>().As<IConfig>().InstancePerRequest();
}
}
And here is my interface definition and class. Which is defined in the same assembly.
public interface IConfig
{
string GetSetting(string key);
T GetSetting<T>(string key);
}
public class Config : IConfig
{
public string GetSetting(string key)
{
return ConfigurationManager.AppSettings[key];
}
public T GetSetting<T>(string key)
{
var setting = GetSetting(key);
return setting != null ? (T)System.Convert.ChangeType(setting, typeof(T)) : default(T);
}
}
This is so ridiculous but I failed to build the services before the container. Oversight on my part and the below works.
BuildServices(builder);
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
config.EnsureInitialized();

unable to setup swagger UI in WEB API .net framework project

i have SwaggerConfig Setup like this
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "My.API");
})
.EnableSwaggerUi(c =>
{
});
}
}
and WebApiConfig setup like this
public static class WebApiConfig
{
/// <summary>
/// Register Method for Configuration Settings.
/// </summary>
/// <returns>Returns a HttpConfiguration which is a complex object which represents a configuration of HttpServer.
/// </returns>
public static HttpConfiguration Register()
{
var config = new HttpConfiguration();
// Web API routes
config.MapHttpAttributeRoutes();
config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());
// SetupOData(config);
// require authentication for all controllers
config.Filters.Add(new AuthorizeAttribute());
config.Filters.Add(new HostAuthenticationAttribute("Bearer"));
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "GetPagedData",
routeTemplate: "{controller}/{pageNumber}/{pageSize}"
);
// Web API configuration and services
config.EnableCors();
// pulling out the XML for now -teg
config.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("application/json-patch+json"));
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("text/html"));
var logger = new LoggerConfiguration()
.ReadFrom.AppSettings()
.CreateLogger();
config.Services.Add(typeof(IExceptionLogger), new MedefisExceptionLogger(logger));
config.Filters.Add(new CommonExceptionFilterAttribute());
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Services.Replace(typeof(IDocumentationProvider),
new XmlCommentDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/Medefis.API.XML")));
return config;
}
}
in the Startup.cs i am calling SwaggerConfig.Register() and few lines later i am also calling WebApiConfig.Register()
public class Startup
{
/// <summary>
/// Configuration for IAppBuilder on MedefisAPIUri
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
var builder = new ContainerBuilder();
SwaggerConfig.Register();
var logger = new LoggerConfiguration()
.ReadFrom.AppSettings()
.CreateLogger();
logger.Information("Api Startup");
AutoMapperConfiguration.Configure();
//CreateFoldersOnFileSystem();
app.UseCors(CorsOptions.AllowAll);
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
AntiForgeryConfig.UniqueClaimTypeIdentifier = IdentityServer3.Core.Constants.ClaimTypes.Subject;
if (ConfigurationManager.AppSettings["UseIdentityServer"] == "True")
{
app.UseIdentityServerBearerTokenAuthentication(
new IdentityServerBearerTokenAuthenticationOptions
{
Authority = MedefisConstants.MedefisSTSConfig,
RequiredScopes = new[] { MedefisConstants.Scopes.api },
ClientId = MedefisConstants.Scopes.api,
ClientSecret = MedefisConstants.Secrets.InternalAPIClientSecret
}
);
}
// https://stackoverflow.com/questions/15637491/how-to-inject-httpcontextbase-using-autofac-in-asp-net-mvc-4
builder.Register(c => new HttpContextWrapper(HttpContext.Current)).As<HttpContextBase>().InstancePerRequest();
builder.RegisterType<ContactEmailRepository>().As<IContactEmailRepository>().InstancePerRequest();
builder.RegisterType<EmailMedefisUsersHelper>().As<IEmailMedefisUsersHelper>().InstancePerRequest();
builder.RegisterType<AgencyBiddingProfileService>().As<IAgencyBiddingProfileService>().InstancePerRequest();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<UploadDocumentHelper>().As<IUploadDocumentHelper>().InstancePerRequest();
builder.RegisterModule(new ValidationModule());
//Service
//builder.RegisterType<..... // su registerations goes here
//Setup
var config = WebApiConfig.Register();
builder.RegisterWebApiFilterProvider(config);
builder.RegisterInstance(config).As<HttpConfiguration>().SingleInstance().PropertiesAutowired();
var container = builder.Build();
config.EnableDependencyInjection();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
app.Use(typeof(WebApiAuthUserMiddleware));
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseWebApi(config);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
config.EnsureInitialized();
}
}
the problem is that when i call http://localhost:xxxx/swagger it redirects to /swagger/ui/index but i get the error
No type was found that matches the controller named 'swagger'
please not the i am using Swashbuckle.Net45 version 5.2.1
Update
after prefixing the paths with "api/" in routeTemplate: "api/{controller}/{id}""
i was able to load the UI but none of paths and definitions are empty.
Solution was not to use SwaggerConfig.Register() separately instead do this configuration inside WebApiConfig.

How to use Ninject to pass Initialized Instance into Web API Controller's Constructor

I am trying to use Ninject to inject dependency into a Self-host Web API Controller. I am success to inject the dependency as follows:
public static IKernel CreateKernel()
{
var kernel = new StandardKernel();
try
{
kernel.Bind<ITestAlert>().To<TestAlert>();
return kernel;
}
catch (Exception)
{
throw;
}
}
and
var server = WebApp.Start(baseAddress, (appBuilder) =>
{
// Configure Web API for self-host.
var config = new HttpConfiguration();
config.DependencyResolver = new NinjectResolver(NinjectConfig.CreateKernel());
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
});
However, what I want to do is to pass an initialized instance from outside into the Controller's constructor. For instance, I have initialized an instance
public interface ITestAlert
{
string Fullname { get; set; }
}
and
public class TestAlert : ITestAlert
{
private string _fullname;
public string Fullname
{
get
{
return _fullname;
}
set
{
_fullname = value;
}
}
}
The instance is initialized as
public ITestAlert _testAlert;
_testAlert = new TestAlert
{
FullName = "Dummy"
};
What I expect is to get "Dummy" output string from the Get action method of the Controller,
private ITestAlert _testAlert = null;
public ValuesController(ITestAlert testAlert)
{
_testAlert = testAlert;
}
public string Get()
{
return _testAlert.Fullname;
}
However, I can only get a "null" return! Would you please advise how to pass the initialized instance into the Controller's constructor. Thanks.
Adding a dedicated binding should do the trick :
kernel.Bind<ITestAlert>()
.ToConstant(new TestAlert{FullName = Dummy"}).WhenInjectedInto<ValuesController>();

Dependency is not getting resolved in WebAPI (including OWIN) with Autofac

My global.asax looks like below
private void BuildIocContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new BootstrapModule());
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); //Set the WebApi DependencyResolver
}
protected void Application_Start()
{
BuildIocContainer();
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
I have built a bootstrap module for autofac like the one below
public class BootstrapModule : Autofac.Module
{
private Assembly AssemblyOf<T>()
{
return typeof(T).Assembly;
}
private Assembly WebAssembly
{
get { return Assembly.GetExecutingAssembly(); }
}
private void RegisterMvc(ContainerBuilder builder)
{
builder.RegisterType<AsyncControllerActionInvoker>()
.As<IActionInvoker>();
builder.RegisterControllers(WebAssembly)
.InjectActionInvoker();
builder.RegisterApiControllers(WebAssembly).InjectActionInvoker();
}
private void RegisterLogger(ContainerBuilder builder)
{
builder.Register(CreateLogger)
.SingleInstance();
builder.Register(_ => new NLogWrapper(LogManager.GetLogger("DefaultLogger")))
.As<Logging.ILogger>()
.SingleInstance();
}
private static System.Func<Type, Logging.ILogger> CreateLogger(IComponentContext componentContext)
{
return type => new NLogWrapper(LogManager.GetLogger(type.FullName));
}
protected override void Load(ContainerBuilder builder)
{
RegisterLogger(builder);
RegisterMvc(builder);
builder.RegisterAssemblyTypes(WebAssembly)
.AsImplementedInterfaces();
builder.RegisterType<UserService>()
.As<IUserService>()
.InstancePerRequest();
builder.RegisterAssemblyTypes(typeof(IUserService).Assembly)
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(typeof(IUserRepository).Assembly)
.AsImplementedInterfaces();
builder.RegisterFilterProvider();
}
}
Now, when I try to hit account controller through postman client,
private IUserService _userService;
public AccountController(IUserService userService)
{
_userService = userService;
}
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public HttpStatusCode Register(User model)
{
if (!ModelState.IsValid)
{
return HttpStatusCode.BadRequest;
}
// TODO : Write mapper if needed
var user = new ToolUser()
{
FirstName = model.FirstName,
LastName = model.LastName,
EmailID = model.EmailID,
DOB = Convert.ToDateTime(model.DateOfBirth),
Gender = model.Gender.ToString(),
TenandID = model.TenantID,
Password = model.Password
};
HttpStatusCode result = _userService.RegisterUser(user);
return result;
}
I get the error
"An error occurred when trying to create a controller of type 'AccountController'. Make sure that the controller has a parameterless public constructor."
What am I doing wrong?
You should have something like
HttpConfiguration config = new HttpConfiguration();
somewhere, which you use to register your routes etc.
Pass that config to your BuildIocContainer() mehod and add the line
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
instead of
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); //Set the WebApi DependencyResolver
Hope that helps

Mock the business layer for integration testing the 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;
}
}
}

Resources