Nancy Ninject Module Construction - c#

I'm trying to test my Nancy modules using Ninject as the IoC container. My problem is that I cannot seem to get Nancy to use my IoC bindings to resolve the NancyModule type.
I am using the latest Nancy on Nuget, the latest Nancy.Bootstrap.Ninject built from source with the latest Ninject.
My test setup is as follows:
[TestFixtureSetUp]
public virtual void ClassSetup()
{
Assembly.Load(typeof (MyModule).Assembly.FullName);
var bootstrapper = new AspHostConfigurationSource();
this.host = new Browser(bootstrapper);
}
[Test]
public void test()
{
/*snip */
var id = entity.Id;
var response = host.Put("/path/to/{0}/".With(id.Encode()),
(with) =>
{
with.HttpRequest();
with.Header("Accept", "application/xml");
});
response.StatusCode.Should().Be(Nancy.HttpStatusCode.OK);
/*snip */
}
That's my test setup, snipped. Now is my host setup (defined in my assembly):
public class MyBootstrapper: Nancy.Bootstrappers.Ninject.NinjectNancyBootstrapper
{
public bool ApplicationContainerConfigured { get; set; }
public Ninject.IKernel Container
{
get { return ApplicationContainer; }
}
public bool RequestContainerConfigured { get; set; }
protected override void ConfigureApplicationContainer(Ninject.IKernel existingContainer)
{
this.ApplicationContainerConfigured = true;
base.ConfigureApplicationContainer(existingContainer);
Nancy.Json.JsonSettings.MaxJsonLength = Int32.MaxValue;
StaticConfiguration.DisableCaches = true;
}
protected override void ConfigureRequestContainer(Ninject.IKernel container, NancyContext context)
{
container.Load(new[] { new ServiceModule() });
}
protected override DiagnosticsConfiguration DiagnosticsConfiguration
{
get { return new DiagnosticsConfiguration { Password = #"12345" }; }
}
}
My IoC bindings are as follows:
Bind<Nancy.NancyModule>()
.ToMethod(context =>
{
return new MyModule1(context.Kernel.Get<IMongoRepository<Guid, Entity>>(
Properties.Settings.Default.NamedCollection1),
context.Kernel.Get<IMongoRepository<Guid, Entity>>(
Properties.Settings.Default.NamedCollection2));
})
.Named(typeof(MyModule1).FullName);
Bind<Nancy.NancyModule>()
.ToMethod(context =>
{
return new MyModule2(context.Kernel.Get<IMongoRepository<Guid, Entity>>(
Properties.Settings.Default.NamedCollection3),
context.Kernel.Get<IMongoRepository<Guid, Entity>>(
Properties.Settings.Default.NamedCollection4));
})
.Named(typeof(MyModule2).FullName);
I want to take control of the construction on the Nancy modules. I've looked through the source for Nancy and it looks like for requests nancy asks the configured IoC container for all registered bindings for the type NancyModule with the appropriate key. Code that follows is defined in the Nancy.Bootstrappers.Ninject assembly
protected override sealed NancyModule GetModuleByKey(IKernel container, string moduleKey)
{
return container.Get<NancyModule>(moduleKey);
}
The key looks to be generated using a key generator object:
public class DefaultModuleKeyGenerator : IModuleKeyGenerator
{
/// <summary>
/// Returns a string key for the given type
/// </summary>
/// <param name="moduleType">NancyModule type</param>
/// <returns>String key</returns>
public string GetKeyForModuleType(Type moduleType)
{
return moduleType.FullName;
}
}
This is why my bindings are set up as named bindings. The idea is that when Nancy requests a named binding (for a module) it will pick p my named bindings.
It is not working as expected. Ninject is complaining that I have multiple bindings setup for the type NancyModule.
To reiterate: my goal is to take control of Nancy module construction.
Any ideas would be greatly appreciated.
P.S. When I talk about injecting dependecies into a module I do /not/ refer to NinjectModules as this question does

Related

Using Scoped services within a hosted service in ASP.NET Core

I have a service that I want to share between other transient services. Right now it's not really a service, but in real life application it will. How would I share my service using dependency injection?
I added some demo code below. The SharedService should be the same object for MyTransientService1 and MyTransientService2 in the "Scope" of MyCreatorService.
The second assert fails, while this is what I'd like to accomplish.
class Program
{
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
services.AddScoped<SharedService>();
services.AddTransient<MyTransientService1>();
services.AddTransient<MyTransientService2>();
services.AddTransient<MyCreatorService>();
services.AddHostedService<MyHostedService>();
});
}
public class SharedService
{
public Guid Id { get; set; }
}
public class MyTransientService1
{
public SharedService Shared;
public MyTransientService1(SharedService shared)
{
Shared = shared;
}
}
public class MyTransientService2
{
public SharedService Shared;
public MyTransientService2(SharedService shared)
{
Shared = shared;
}
}
public class MyCreatorService
{
public MyTransientService1 Service1;
public MyTransientService2 Service2;
public MyCreatorService(MyTransientService1 s1, MyTransientService2 s2)
{
Service1 = s1;
Service2 = s2;
}
}
public class MyHostedService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public MyHostedService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
var creator1 = _serviceProvider.GetRequiredService<MyCreatorService>();
var creator2 = _serviceProvider.GetRequiredService<MyCreatorService>();
Assert.That(creator1.Service1.Shared.Id, Is.EqualTo(creator1.Service2.Shared.Id));
Assert.That(creator1.Service1.Shared.Id, Is.Not.EqualTo(creator2.Service1.Shared.Id));
return Task.CompletedTask;
}
}
If you use AddScoped, the instance will be the same within the request (for instance for a HTTP Request & Response). If you need your other services to be created everytime they are resolved, you can indeed use AddTransient, but otherwise you can also use AddScoped.
I would also suggest you bind MyHostedService in this manner (if it has anything to do with your described problem), since it seems to be providing a Singleton binding. If a scope of an outer service (one with injected dependencies) is narrower, it will hold hostage injected dependencies. A singleton service with transient dependencies will therefore make all its dependencies singleton, since the outer service will only be created once and its dependencies only resolved once.
UPDATE
After understanding the problem more clearly this should work for you (no other bindings needed):
services.AddTransient<MyCreatorService>(_ =>
{
var shared = new SharedService();
shared.Id = Guid.NewGuid();
return new MyCreatorService(
new MyTransientService1(shared),
new MyTransientService2(shared));
});
Add a constructor to the SharedService class, otherwise the Id is always 000.
Use the following code to create a different scope, so the SharedService will be initialized twice:
MyCreatorService creator1;
MyCreatorService creator2;
using (var scope1 = _serviceProvider.CreateScope())
{
creator1 = scope1.ServiceProvider.GetRequiredService<MyCreatorService>();
}
using (var scope2 = _serviceProvider.CreateScope())
{
creator2 = scope2.ServiceProvider.GetRequiredService<MyCreatorService>();
}

Is there a way to Mock a View in EF Core?

I'm using EF Core "In Memory" database but having trouble mocking a view.
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "TestDatabase")
.Options;
// Insert seed data into the database using one instance of the context
using (var context = new MyContext(options))
{
// insert test values into entities here (omitted)...
// I want some test values for a View
context.MyView.AddRange( viewEntity1, viewEntity, ... etc );
context.SaveChanges();
}
At runtime, when attempting to add the view entities, the following exception is raised:
System.InvalidOperationException: Unable to track an instance of type
'MyView' because it does not have a primary key. Only entity types
with primary keys may be tracked.
The reason I want to mock the View is that I want to test some LINQ queries that join to the View.
Any ideas?
// Entity
public class MyView
{
public string UserId {get;set;}
public int CentreId {get;set;}
}
// Context
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
modelBuilder.Entity<MyView>()
.HasNoKey()
.ToView("MyView");
}
No, the in-memory provider doesn't support views. It's a relational operation, same as the FromSql* and ExecuteSql* methods.
AddRange won't work on a view regardless of the provider.
You can test views; there are a couple of options:
Do integration tests with a real database
Use a better provider, possibly SQLite
Use a testing package that allows you to mock the view
I maintain one of said testing packages, EntityFrameworkCore.Testing, which does allow you to easily unit test SUTs that depend on views.
You should not mock anything at the Entity Framework level. Don't mock what you don't own.
In general, you should not be mocking implementation detail, which is what a view is - The consumer of your code should not need to know that it's interacting with a view, but that it's interacting with some data source. Otherwise your tests will end up breaking when you change the implementation detail, which is not what should be happening.
If you want to test how your controllers (or similar) interact with Entity Framework, you should use the Repository pattern and mock the Repository instead. If that's the case, you should not be mocking anything and instead should use a real database to test against.
If you want to test how your repositories interact with the database, you can use an integration test to test this. Repositories should be simple with very little business logic, so there is not much reason to unit test them to begin with.
Yes, you sure can....
Here is what I am using for my setup
Moq (for...mocks)
Lamar (for Dependency Injection)
STANDARD EF CORE VIEW:
Here the code for one of my view-definitions.
// EntityConfiguration
internal class ProjectStatusDetailConfiguration : IEntityTypeConfiguration<ProjectStatusDetail>
{
public void Configure(EntityTypeBuilder<ProjectStatusDetail> builder)
{
// SQL View
builder.ToView("vProjectStatus", "dbo")
.HasKey(e => e.ProjectId);
}
}
// Concrete DbContext
public class ProjectDbContext : DbContext
{
private IConfigurationRoot _settings;
public ProjectDbContext()
{
// NOTE: You must have this constructor for Migrations to work
BuildAppConfiguration();
}
public ProjectDbContext(DbContextOptions<ProjectDbContext> options) : base(options)
{
// NOTE: This constructor is used at the IServiceProvider layer
BuildAppConfiguration();
}
public virtual DbSet<Project> Project { get; set; }
public virtual DbSet<ProjectStatusDetail> vProjectStatusDetail { get; set; }
private void BuildAppConfiguration()
{
var builder = new ConfigurationBuilder();
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
_settings = builder.Build();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Default Configuration (if none is provided by the app)
if (!optionsBuilder.IsConfigured)
optionsBuilder.UseSqlServer(_settings.GetConnectionString(JsonSettings.ConnectionStrings.DefaultDb));
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// GLOBAL Configurations
builder.ApplyGlobalEntityConfiguration(new AuditableStandardUserNameGlobalConfiguration());
// ENTITY Configurations
builder.ApplyConfiguration(new ProjectConfiguration());
// SQL VIEW Configurations
builder.ApplyConfiguration(new ProjectStatusDetailConfiguration());
}
}
// Lamar ServiceRegistry (for the business-layer)
public class ContainerRegistry : ServiceRegistry
{
public ContainerRegistry()
{
Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.LookForRegistries();
scan.SingleImplementationsOfInterface();
});
// --------
// DATABASE
For<DbContext>().Use<ProjectDbContext>();
For(typeof(IAuditableRepository<>)).Use(typeof(GenericAuditableRepository<>));
ForConcreteType<ProjectUnitOfWork>().Configure.Setter<DbContext>().Is<ProjectDbContext>();
// Policies
Policies.Add<GenericAuditableRepositoryConfiguredInstancePolicy>(); //<-- Sets CTOR at Runtime
Policies.Add<UnitOfWorkConfiguredInstancePolicy>(); //<-- Sets CTOR at Runtime
}
}
// SINGLE POINT OF ENTRY: Consumes & "serves-up" your App's Registry
public static class IoC
{
public static ServiceRegistry Build()
{
var registry = new ServiceRegistry();
// Registration
registry.IncludeRegistry<ContainerRegistry>();
return new ServiceRegistry(registry);
}
}
UNIT TESTING SETUP:
Here the code for my unit testing setup. Obviously, I am still working-on the DbSetProxy's query...but here it is anyway.
// Lamar ServiceRegistry (for the unit test)
internal class ContainerRegistry : ServiceRegistry
{
public ContainerRegistry()
{
// This is where you will SHIM all your Stubs/Fakes
For<IWindowsIdentityHelper>().Use<UnitTests.WindowsIdentityHelperStub>(); //<-- Stub
}
}
// SINGLE POINT OF ENTRY: Consume all Registry's (for the unit testing-layer)
public static class IoC
{
public static ServiceRegistry Build()
{
var registry = new ServiceRegistry();
// Notice you can bring-in many ServiceRegistry's
// Just be sure to apply your "stubbing" registry last
registry.IncludeRegistry<Bushido.WorkflowComponent.DependencyResolution.ContainerRegistry>();
registry.IncludeRegistry<Bushido.Project.Business.DependencyResolution.ContainerRegistry>();
registry.IncludeRegistry<Bushido.WorkflowComponent.UnitTests.DependencyResolution.ContainerRegistry>();
return new ServiceRegistry(registry);
}
}
// This base class class does the heavy-lifting
public class UnitTestBase
{
protected IContainer Container { get; private set; }
protected Builder Builder { get; set; } = new Builder();
[TestInitialize()]
public virtual void Initialize()
{
IHostBuilder hostBuilder = CreateHostBuilder(null);
IHost host = hostBuilder.Build();
IServiceScope serviceScope = host.Services.CreateScope();
IServiceProvider serviceProvider = serviceScope.ServiceProvider;
Container = serviceProvider.GetRequiredService<IContainer>();
}
[TestCleanup()]
public virtual void Cleanup()
{
Container.Dispose();
Container = null;
}
private IHostBuilder CreateHostBuilder(string[] args)
{
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
// You must apply the Lamar Factory
.UseServiceProviderFactory<ServiceRegistry>(new LamarServiceProviderFactory())
.ConfigureServices((hostContext, services) =>
{
var databaseName = Guid.NewGuid().ToString();
// Add Lamar & Apply Scoping here (not in your ServiceRegistry's)
services.AddLamar(IoC.Build());
services.AddScoped<ProjectUnitOfWork>();
services.AddScoped<WorkflowComponentUnitOfWork>();
services.AddScoped<ProjectDbContext>();
services.AddScoped<WorkflowComponentDbContext>();
services.AddDbContext<ProjectDbContext>((provider, options) => options.UseInMemoryDatabase(databaseName), ServiceLifetime.Singleton);
services.AddDbContext<WorkflowComponentDbContext>((provider, options) => options.UseInMemoryDatabase(databaseName), ServiceLifetime.Singleton);
});
return builder;
}
}
// This base class contains the common setup for your VIEWS DbSetProxy
public class DataScenarioBase
{
protected static DbSet<TEntity> CreateDbSet<TEntity>(IQueryable<TEntity> collection) where TEntity : class
{
var stubDbSet = new Mock<DbSet<TEntity>>();
stubDbSet.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(collection.Provider);
stubDbSet.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(collection.Expression);
stubDbSet.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(collection.ElementType);
stubDbSet.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(collection.GetEnumerator());
return stubDbSet.Object;
}
}
// This is a sample DataScenario
public class ProjectDefaultDataScenario : DataScenarioBase
{
public void Load(IContainer container)
{
var unitOfWork = container.GetInstance<ProjectUnitOfWork>();
// DATA
LoadDefaultData(unitOfWork) //<-- you can pre-load default data into the UnitOfWork here too
// VIEWS
var dbContext = ((ProjectDbContext)unitOfWork.DbContext);
dbContext.vProjectStatusDetail = CreateDbSet<ProjectStatusDetail>(ProjectStatusDetailQuery(unitOfWork));
// Inject
container.GetNestedContainer().Inject(unitOfWork); //<-- This (along with scoping) ensures the right instance is always passed back
}
private IQueryable<ProjectStatusDetail> ProjectStatusDetailQuery(ProjectUnitOfWork unitOfWork)
{
var query = (from project in unitOfWork.Project
select new ProjectStatusDetail
{
ProjectId = project.Id,
ProjectName = project.ProjectName,
ObjectStateName = "Testing 123",
ObjectStateDateTime = DateTime.Now,
});
return query;
}
}
// Here is a sample Unit Test
[TestMethod]
public void ProjectProvider_WorkflowProvider_ObjectState_New()
{
// -----
// ARRANGE
Builder.WorkflowDefaultSetup.Load(Container);
Builder.ProjectDefaultSetup.Load(Container);
var project = new Project();
project.ProjectName = "Red Barchetta";
project.Description = "To keep it as new has been our dearest dream";
// -----
// ACT
var provider = Container.GetRequiredService<ProjectProvider>();
provider.CreateProject(project);
// -----
// ASSERT
Assert.IsTrue(project.Id > 0);
Assert.IsNotNull(project.ObjectState);
Assert.IsInstanceOfType(project.ObjectState, typeof(Bushido.Project.Business.StateManagement.Project.New));
}

No constructor for type 'MyProject.Response' can be instantiated using services from the service container and default values

I am using asp.net core 3.1 project template to develop a web API. There are no compilation errors.
Here goes my code details:
Program.cs
public class Program
{
public static void Main(string[] args)
{
// Use the W3C Trace Context format to propagate distributed trace identifiers.
// See https://devblogs.microsoft.com/aspnet/improvements-in-net-core-3-0-for-troubleshooting-and-monitoring-distributed-apps/
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup.cs
public class Startup
{
private readonly IConfiguration configuration;
private readonly IWebHostEnvironment webHostEnvironment;
/// <summary>
/// Initializes a new instance of the <see cref = "Startup"/> class.
/// </summary>
/// <param name = "configuration">The application configuration, where key value pair settings are stored. See
/// http://docs.asp.net/en/latest/fundamentals/configuration.html</param>
/// <param name = "webHostEnvironment">The environment the application is running under. This can be Development,
/// Staging or Production by default. See http://docs.asp.net/en/latest/fundamentals/environments.html</param>
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
this.configuration = configuration;
this.webHostEnvironment = webHostEnvironment;
}
/// <summary>
/// Configures the services to add to the ASP.NET Core Injection of Control (IoC) container. This method gets
/// called by the ASP.NET runtime. See
/// http://blogs.msdn.com/b/webdev/archive/2014/06/17/dependency-injection-in-asp-net-vnext.aspx
/// </summary>
public virtual void ConfigureServices(IServiceCollection services) =>
services
.AddCosmosDBConfiguration(configuration)
.AddAutoMapperConfiguration()
.AddCustomResponseCompression(configuration)
.AddCustomCors()
.AddCustomOptions(configuration)
.AddHttpContextAccessor()
.AddCustomRouting()
.AddCustomStrictTransportSecurity()
.AddCustomHealthChecks()
.AddServerTiming()
.AddControllers()
.AddCustomJsonOptions(webHostEnvironment)
.AddCustomMvcOptions(configuration)
.Services
.AddCustomGraphQL(configuration, webHostEnvironment)
.AddGraphQLResolvers()
.AddGraphQLResponse()
.AddProjectRepositories()
.AddProjectSchemas();
/// <summary>
/// Configures the application and HTTP request pipeline. Configure is called after ConfigureServices is
/// called by the ASP.NET runtime.
/// </summary>
public virtual void Configure(IApplicationBuilder application) =>
application
.UseIf(
this.webHostEnvironment.IsDevelopment(),
x => x.UseServerTiming())
.UseForwardedHeaders()
.UseResponseCompression()
.UseFetchLocaleMiddleware()
.UseIf(
!this.webHostEnvironment.IsDevelopment(),
x => x.UseHsts())
.UseIf(
this.webHostEnvironment.IsDevelopment(),
x => x.UseDeveloperExceptionPage())
.UseRouting()
.UseCors(CorsPolicyName.AllowAny)
.UseEndpoints(
builder =>
{
builder
.MapHealthChecks("/status")
.RequireCors(CorsPolicyName.AllowAny);
builder
.MapHealthChecks("/status/self", new HealthCheckOptions() { Predicate = _ => false })
.RequireCors(CorsPolicyName.AllowAny);
})
.UseWebSockets()
// Use the GraphQL subscriptions in the specified schema and make them available at /graphql.
.UseGraphQLWebSockets<MainSchema>()
// Use the specified GraphQL schema and make them available at /graphql.
.UseGraphQL<MainSchema>()
.UseIf(
this.webHostEnvironment.IsDevelopment(),
x => x
// Add the GraphQL Playground UI to try out the GraphQL API at /.
.UseGraphQLPlayground(new GraphQLPlaygroundOptions() { Path = "/" })
// Add the GraphQL Voyager UI to let you navigate your GraphQL API as a spider graph at /voyager.
.UseGraphQLVoyager(new GraphQLVoyagerOptions() { Path = "/voyager" }));
}
Response.cs
public class Response
{
public object Data { get; set; }
public string StatusCode { get; set; }
public string ErrorMessage { get; set; }
public Response(object data)
{
StatusCode = "Success";
Data = data;
}
public Response(string statusCode, string errorMessage)
{
StatusCode = statusCode;
ErrorMessage = errorMessage;
}
}
All the dependencies mentioned in the ConfigureServices of the Startup.cs are available. While validating the APIs I am getting a run time error as mentioned below:
No constructor for type 'MyProject.Response' can be instantiated using services from the service container and default values.
Here goes the dependency setup required for the Response class as mentioned below:
ProjectServiceCollectionExtensions.cs
namespace MyProject
{
public static class ProjectServiceCollectionExtensions
{
public static IServiceCollection AddGraphQLResponse(this IServiceCollection services) => services.AddScoped<Response>();
}
}
Resolver.cs
public class Resolver
{
public Response Response(object data)
{
return new Response(data);
}
public Response Error(GraphQLError error)
{
return new Response(error.StatusCode, error.ErrorMessage);
}
public Response AccessDeniedError()
{
var error = new AccessDeniedError();
return new Response(error.StatusCode, error.ErrorMessage);
}
public Response NotFoundError(string id)
{
var error = new NotFoundError(id);
return new Response(error.StatusCode, error.ErrorMessage);
}
}
CountriesResolver.cs
using Author.Core.Framework.Utilities;
using Author.Query.New.API.GraphQL.Types;
using Author.Query.Persistence.DTO;
using Author.Query.Persistence.Interfaces;
using GraphQL.DataLoader;
using GraphQL.Types;
using Microsoft.AspNetCore.Http;
using System;
namespace MyProject.GraphQL.Resolvers
{
public class CountriesResolver : Resolver, ICountriesResolver
{
private readonly ICountryService _countryService;
private readonly IHttpContextAccessor _accessor;
private readonly IUtilityService _utilityService;
private readonly IDataLoaderContextAccessor _dataLoaderContextAccessor;
public CountriesResolver(ICountryService countryService, IHttpContextAccessor accessor, IUtilityService utilityService, IDataLoaderContextAccessor dataLoaderContextAccessor)
{
_countryService = countryService ?? throw new ArgumentNullException(nameof(countryService));
_accessor = accessor;
_utilityService = utilityService ?? throw new ArgumentNullException(nameof(utilityService));
_dataLoaderContextAccessor = dataLoaderContextAccessor;
}
public void Resolve(GraphQLQuery graphQLQuery)
{
var language = _accessor.HttpContext.Items["language"] as LanguageDTO;
graphQLQuery.FieldAsync<ResponseGraphType<CountryResultType>>("countriesresponse", resolve: async context =>
{
if (language != null)
{
var loader = _dataLoaderContextAccessor.Context.GetOrAddLoader("GetAllCountries", () => _countryService.GetAllCountriesAsync(language));
var list = await context.TryAsyncResolve(async c => await loader.LoadAsync());
return Response(list);
}
return null;
}
, description: "All Countries data");
graphQLQuery.FieldAsync<ResponseGraphType<CountryType>>("country", arguments: new QueryArguments(new QueryArgument<NonNullGraphType<IntGraphType>>{Name = "countryId", Description = "id of the country"}), resolve: async context =>
{
var countryId = context.GetArgument<int>("countryId");
if (language != null && countryId > 0)
{
var loader = _dataLoaderContextAccessor.Context.GetOrAddLoader("GetCountry", () => _countryService.GetCountryAsync(language, countryId));
var countryDetails = await context.TryAsyncResolve(async c => await loader.LoadAsync());
return Response(countryDetails);
}
return null;
}
);
}
}
}
Can anyone help me to fix this issue by providing their guidance
Based on how Response is used by Resolver I would say Response does not need to be added to the DI/IoC container, since Resolver is essentially a Response factory
If Response is not explicitly injected anywhere then the container does not need to be aware of it. No need to add it to the container begin with.
Remove
//...
.AddGraphQLResponse() //<--SHOULD BE REMOVED
//...
extension from Startup.ConfigureServices

How to allow a plugin webAPI to override an existing webAPI

I have a Web API controller, say EmployeeController, which we register using Autofac. Now we create another controller with the same name and route, but with different functionality. When we try to register this new EmployeeController (i.e., Plugin) using Autofac, we would get an exception like
multiple types were found that match the controller named EmployeeController.
My objective is to successfully inject the second controller and override the functionality of the first controller with it.
Project A - > Core Project
namespace Main.API
{
public class EmployeeController : ApiController
{
// Some Logic
}
}
Project B - > Plug-in Project
Later consumer want to override employee controller with same controller name
namespace Plugin.API
{
public class EmployeeController : ApiController
{
// Some Logic
}
}
Autofac
// assemblies contains Main.API.dll & Plugin.API.dll
builder.RegisterApiControllers(assemblies.ToArray()).InstancePerRequest();
In order to implement what you want I would use AOP concept which will make it easier to implement and more powerful.
Castle DynamicProxy project provides AOP concept for .net and can be used by Autofac with the Autofac.Extras.DynamicProxy2 nuget package.
You will have only 1 EmployeeController in your main project
namespace Main.API
{
public class EmployeeController : ApiController
{
public virtual String Get(Int32 id)
{
// Some Logic
}
}
}
and various IInterceptor in your plugin projects :
namespace Plugin
{
public class XEmployeeeControllerInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if(!invocation.Method.Name == nameof(Core.APi.EmployeeController.Get))
{
return;
}
invocation.Proceed();
// alter return value
invocation.ReturnValue = invocation.ReturnValue + "-intercepted";
}
}
}
Then register things like this:
builder.RegisterApiControllers(assemblies.ToArray())
.InstancePerRequest()
.EnableClassInterceptors();
builder.RegisterAssemblyTypes(assemblies.ToArray())
.As<IInterceptor>();
See Type Interceptors for more information
Using this following code snippet you can override the same name of plugin controller.
public class CustomHttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
/// <summary>
/// custom http controllerselector
/// </summary>
/// <param name="config"></param>
public CustomHttpControllerSelector(HttpConfiguration config) : base(config)
{
_configuration = config;
_controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
}
/// <summary>
/// GetControllerMapping
/// </summary>
/// <returns></returns>
public override IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
{
return _controllers.Value;
}
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
var controllers = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (Type t in controllerTypes)
{
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
//Remove Core API Controller and add the Plugin API controller.
if (controllers.Keys.Contains(controllerName) && t.Namespace.Contains("Plugin"))
{
controllers.Remove(controllerName);
}
if (!controllers.Keys.Contains(controllerName))
{
controllers[controllerName] = new HttpControllerDescriptor(_configuration, t.Nam`enter code here`e, t);
}
}
return controllers;
}
}

Ninject, WCF & Auto Discovery

I can't seem to get these 3 working together.
I narrowed it down to a very simple service with 1 method:
[System.ServiceModel.ServiceContract]
public interface Icontract
{
[System.ServiceModel.OperationContract]
void Ping();
}
public class contract : Icontract
{
public void Ping()
{ }
}
I have a factory that looks like this:
public class ServiceFactory
{
private readonly IKernel _kernel;
public ServiceFactory(IKernel kernel)
{
_kernel = kernel;
}
public NinjectServiceHost<T> GetService<T>()
{
return _kernel.Get<NinjectServiceHost<T>>();
}
}
If I create the service like so...
_tmp = new ServiceHost(typeof(ConsoleApplication1.contract));
_tmp.Open();
...Discovery works just fine. However if I use the factory like so...
_tmp = _factory.GetService<ConsoleApplication1.contract>();
_tmp.Open();
...the service isn't discoverable anymore. Everything else about the service works as expected.
Anyone had any joy getting Discovery working this way, or is there something I'm doing wrong?
It is a bug in Ninject.Extensions.Wcf.NinjectServiceBehavior class.
Just create your own NinjectServiceBehaviorFixed class derived from IServiceBehavior interface with following method:
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (EndpointDispatcher endpointDispatcher in serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>().SelectMany(channelDispatcher => (IEnumerable<EndpointDispatcher>) channelDispatcher.Endpoints))
{
if (endpointDispatcher.DispatchRuntime.InstanceProvider == null)
{
endpointDispatcher.DispatchRuntime.InstanceProvider = _instanceProviderFactory(serviceDescription.ServiceType);
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(_requestScopeCleanUp);
}
}
}
Add to your NinjectModule code:
Unbind<IServiceBehavior>();
Bind<IServiceBehavior>().To<NinjectServiceBehaviorFixed>();
In ApplyDispatchBehavior method we just added check if endpointDispatcher.DispatchRuntime.InstanceProvider is null because WS-Discovery creates new endpoints with defined InstanceProvider which should not be overwritten
Where are you setting up your bindings? Somewhere in your code you'll need to initialize the kernel with a ServiceModule like this:
_kernel = new StandardKernel(new YourNinjectModule());
And then in the module code:
public class YourNinjectModule: NinjectModule
{
/// <summary>
/// Loads the module into the kernel.
/// </summary>
public override void Load()
{
// ===========================================================
//
// Bind dependency injection.
// Add entries in here for any new services or repositories.
//
// ===========================================================
this.Bind<Icontract>().To<contract>();
}
}

Categories

Resources