ElasticClient settings not applied when running in separate tests - nest

We are running multiple unit tests, each of them creates it's own instance of ElasticClient. Some of the tests use "StringEnumConverter" to convert enums, other will use the default convertor (into integers). The problem is that it seems the settings of connection is cached somewhere and it's only the first test that decides how enums will be converted.
This is not a threading issue, since we are running the tests sequentially.
The constructor of the TestBase class looks like this:
public TestsBase(bool serializeEnumsToString = false)
{
var node = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(
node,
defaultIndex: "my-application" + Guid.NewGuid().ToString()
);
if (serializeEnumsToString)
{
Console.Write("Setting up the enums convertor");
settings.AddContractJsonConverters(type =>
{
// TypeCheck to return StringEnumConverter for Enums and Nullable<T> where T : Enum
if (type.IsEnum ||
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof (Nullable<>) &&
type.GetGenericArguments().First().IsEnum))
{
return new StringEnumConverter();
}
return null;
});
}
client = new ElasticClient(settings);
}
Is it possible that there is a cache (on appdomain level?) that would be used any time new instance of ElasticClient is created?

Related

How to unit test azure function using Xunit and Moq

I am very new to unit tests and recently started learning it from various online resources.
But still it confuses me when I need to implement it in my code.
For the given image which I have attached here, could anyone of you suggest me how should I start or where to start?
This is Azure function which I will be creating unit test for, framework/library I would prefer is Xunit and moq.
As mentioned in a comment, a good place to start when unit testing is looking at your code and identifying the different "paths" it can take and what the result of that path will be.
if (inventoryRequest != null)
{
// path 1
await _inventoryService.ProcessRequest(inventoryRequest);
_logger.LogInformation("HBSI Inventory Queue trigger function processed.");
}
else
{
// path 2
_logger.LogInformation("Unable to process HBSI Rate plan Queue.");
}
In your code, because of your if statement, there are 2 possible paths which will end in 2 different results = 2 unit tests.
Now you can start creating your unit tests but first you need to find out what you need to set up to be able to trigger your code.
private readonly ILogger _logger;
private readonly IInventoryService _inventoryService;
public InventoryServiceBusFunction(ILogger logger, IInventoryService inventoryService)
{
_logger = logger;
_inventoryService = inventoryService;
}
You have some dependencies being passed into your constructor with interfaces - great, this means we can mock them. We want to mock dependencies in unit tests because we want to control their behaviour for the tests. Also, mocking the dependencies negates any "real" behaviour the dependency might be performing i.e. database operations, API calls etc.
Using Moq we can mock the objects like so:
public class InventoryServiceBusFunctionTests
{
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();
private readonly Mock<IInventoryService> _mockInventoryService = new Mock<IInventoryService>();
...
We will use these mocks later to make verifications on behaviour we expect to happen.
Next, we need to create an instance of the actual class we want to test.
// using a constructor in the test class will run this code before each test
public InventoryServiceBusFunctionTests()
{
// pass the mocked objects to initialize class
_inventoryServiceBusFunction = new InventoryServiceBusFunction(_mockLogger.Object, _mockInventoryService.Object);
}
Now that we have an instance of the InventoryServiceBusFunction class, we can use any of the public properties/methods in our tests.
[Fact]
public async Task GivenInventoryRequest_WhenFunctionRuns_ThenInventoryServiceProcessesRequest()
{
Now, remembering the paths from earlier, we can start to create the test cases. We can take the first path and create a [Fact] for it. You want to give your test case a meaningful name. I usually use the style of Given_When_Then to describe what is expected to happen.
Next, I usually add 3 comment sections to my test case:
// arrange
// act
// assert
This allows me to clearly see which parts of the test are doing what.
// act
await _inventoryServiceBusFunction.Run(inventoryRequest);
Next, I would fill in the \\ act section because this will tell me (via Intellisense) what I need to arrange. e.g. above, when hovering my mouse over the Run method, I can see that I need to pass an instance of InventoryRequest.
// arrange
var inventoryRequest = new InventoryRequest
{
Name = "abc123",
Quantity = 2,
Tags = new List<string>
{
"foo"
}
};
In the \\ arrange section, initialize an instance of the InventoryRequest class and set the properties. This can be any data as we aren't really interested in the data itself but more what happens when the code runs.
if (inventoryRequest != null)
{
// path 1
await _inventoryService.ProcessRequest(inventoryRequest);
_logger.LogInformation("HBSI Inventory Queue trigger function processed.");
}
Lastly, the \\ assert section. Here, we want to make assertions on what we expect to happen given the set up of the test. So given the InventoryRequest is not null, we expect the if to evaluate to true and we expect the _inventoryService.ProcessRequest(inventoryRequest) method to be executed.
// assert
_mockInventoryService
.Verify(x => x.ProcessRequest(It.Is<InventoryRequest>(ir => ir.Name == inventoryRequest.Name
&& ir.Quantity == inventoryRequest.Quantity
&& ir.Tags.Contains(inventoryRequest.Tags[0]))));
In Moq, we can use the .Verify() method on the mock object to assert that the method was called. We can use the It.Is<T> syntax to make assertions on the data that is passed to the method.
Here is the full test case for path 1:
[Fact]
public async Task GivenInventoryRequest_WhenFunctionRuns_ThenInventoryServiceProcessesRequest()
{
// arrange
var inventoryRequest = new InventoryRequest
{
Name = "abc123",
Quantity = 2,
Tags = new List<string>
{
"foo"
}
};
// act
await _inventoryServiceBusFunction.Run(inventoryRequest);
// assert
_mockInventoryService
.Verify(x => x.ProcessRequest(It.Is<InventoryRequest>(ir => ir.Name == inventoryRequest.Name
&& ir.Quantity == inventoryRequest.Quantity
&& ir.Tags.Contains(inventoryRequest.Tags[0]))));
}
Then for path 2, you are setting up the test so that the else condition is executed.
[Fact]
public async Task GivenInventoryRequestIsNull_WhenFunctionRuns_ThenInventoryServiceDoesNotProcessRequest()
{
// arrange
InventoryRequest inventoryRequest = null;
// act
await _inventoryServiceBusFunction.Run(inventoryRequest);
// assert
_mockInventoryService
.Verify(x => x.ProcessRequest(It.IsAny<InventoryRequest>()), Times.Never);
}
Note - in the \\ assert here, I am asserting that the await _inventoryService.ProcessRequest(inventoryRequest) method is never called. This is because you want the test to fail in this scenario as the method should only be executed in the if condition. You may also choose to verify that the logger method is called with the correct message.

Build/Test verification for missing implementations of query/commands in MediatR

We're using MediatR heavily in our LoB application, where we use the command & query pattern.
Often, to continue in development, we make the commands and the queries first, since they are simple POCOs.
This sometimes can lead to forgetting to create an actual command handler/query handler. Since there's no compile-time validation if there is actually an implementation for the query/command, I was wondering what would be the best approach to see if there's an implementation and throw an error if not, before being able to merge into master.
My idea so far:
Create a two tests, one for queries and one for commands, that scan all the assemblies for an implementation of IRequest<TResponse>, and then scan the assemblies for an associated implementation of IRequestHandler<TRequest, TResponse>
But this would make it still required to first execute the tests (which is happening in the build pipeline), which still depends on the developer manually executing the tests (or configuring VS to do so after compile).
I don't know if there's a compile-time solution for this, and even if that would be a good idea?
We've gone with a test (and thus build-time) verification;
Sharing the code here for the actual test, which we have once per domain project.
The mediator modules contain our query/command(handler) registrations, the infrastructure modules contain our handlers of queries;
public class MissingHandlersTests
{
[Fact]
public void Missing_Handlers()
{
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(typeof(MediatorModules).Assembly);
assemblies.Add(typeof(InfrastructureModule).Assembly);
var missingTypes = MissingHandlersHelpers.FindUnmatchedRequests(assemblies);
Assert.Empty(missingTypes);
}
}
The helper class;
public class MissingHandlersHelpers
{
public static IEnumerable<Type> FindUnmatchedRequests(List<Assembly> assemblies)
{
var requests = assemblies.SelectMany(x => x.GetTypes())
.Where(t => t.IsClass && t.IsClosedTypeOf(typeof(IRequest<>)))
.ToList();
var handlerInterfaces = assemblies.SelectMany(x => x.GetTypes())
.Where(t => t.IsClass && (t.IsClosedTypeOf(typeof(IRequestHandler<>)) || t.IsClosedTypeOf(typeof(IRequestHandler<,>))))
.SelectMany(t => t.GetInterfaces())
.ToList();
List<Type> missingRegistrations = new List<Type>();
foreach(var request in requests)
{
var args = request.GetInterfaces().Single(i => i.IsClosedTypeOf(typeof(IRequest<>)) && i.GetGenericArguments().Any() && !i.IsClosedTypeOf(typeof(ICacheableRequest<>))).GetGenericArguments().First();
var handler = typeof(IRequestHandler<,>).MakeGenericType(request, args);
if (handler == null || !handlerInterfaces.Any(x => x == handler))
missingRegistrations.Add(handler);
}
return missingRegistrations;
}
}
If you are using .Net Core you could the Microsoft.AspNetCore.TestHost to create an endpoint your tests could hit. Sort of works like this:
var builder = WebHost.CreateDefaultBuilder()
.UseStartup<TStartup>()
.UseEnvironment(EnvironmentName.Development)
.ConfigureTestServices(
services =>
{
services.AddTransient((a) => this.SomeMockService.Object);
});
this.Server = new TestServer(builder);
this.Services = this.Server.Host.Services;
this.Client = this.Server.CreateClient();
this.Client.BaseAddress = new Uri("http://localhost");
So we mock any http calls (or any other stuff we want) but the real startup gets called.
And our tests would be like this:
public SomeControllerTests(TestServerFixture<Startup> testServerFixture)
: base(testServerFixture)
{
}
[Fact]
public async Task SomeController_Returns_Titles_OK()
{
var response = await this.GetAsync("/somedata/titles");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var responseAsString = await response.Content.ReadAsStringAsync();
var actualResponse = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<string>>(responseAsString);
actualResponse.Should().NotBeNullOrEmpty();
actualResponse.Should().HaveCount(20);
}
So when this test runs, if you have not registered your handler(s) it will fail! We use this to assert what we need (db records added, response what we expect etc) but it is a nice side effect that forgetting to register your handler gets caught at the test stage!
https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api

Checking exceptions with TestCaseData parameters

I'm using NUnit 3 TestCaseData objects to feed test data to tests and Fluent Assertions library to check exceptions thrown.
Typically my TestCaseData object contains two parameters param1 and param2 used to create an instance of some object within the test and upon which I then invoke methods that should/should not throw exceptions, like this:
var subject = new Subject(param1, param2);
subject.Invoking(s => s.Add()).Should().NotThrow();
or
var subject = new Subject(param1, param2);
subject.Invoking(s => s.Add()).Should().Throw<ApplicationException>();
Is there a way to pass NotThrow() and Throw<ApplicationException>() parts as specific conditions in a third parameter in TestCaseData object to be used in the test? Basically I want to parameterize the test's expected result (it may be an exception of some type or no exception at all).
[TestCaseData] is meant for Test Case Data, not for assertions methods.
I would keep the NotThrow and Throw in separate tests to maintain readability.
If they share a lot of setup-logic, I would extract that into shared methods to reduce the size of the test method bodies.
TestCaseData accepts compile time values, whereas TestCaseSource generates them on runtime, which would be necessary to use Throw and NotThrow.
Here's a way to do it by misusing TestCaseSource.
The result is an unreadable test method, so please don't use this anywhere.
Anyway here goes:
[TestFixture]
public class ActionTests
{
private static IEnumerable<TestCaseData> ActionTestCaseData
{
get
{
yield return new TestCaseData((Action)(() => throw new Exception()), (Action<Action>)(act => act.Should().Throw<Exception>()));
yield return new TestCaseData((Action)(() => {}), (Action<Action>)(act => act.Should().NotThrow()));
}
}
[Test]
[TestCaseSource(typeof(ActionTests), nameof(ActionTestCaseData))]
public void Calculate_Success(Action act, Action<Action> assert)
{
assert(act);
}
}
I ended up using this:
using ExceptionResult = Action<System.Func<UserDetail>>;
[Test]
[TestCaseSource(typeof(UserEndpointTests), nameof(AddUserTestCases))]
public void User_Add(string creatorUsername, Role role, ExceptionResult result)
{
var endpoint = new UserEndpoint(creatorUsername);
var person = GeneratePerson();
var request = GenerateCreateUserRequest(person, role);
// Assertion comes here
result(endpoint.Invoking(e => e.Add(request)));
}
private static IEnumerable AddUserTestCases
{
get
{
yield return new TestCaseData(TestUserEmail, Role.User, new ExceptionResult(x => x.Should().Throw<ApplicationException>())
.SetName("{m} (Regular User => Regular User)")
.SetDescription("User with Regular User role cannot add any users.");
yield return new TestCaseData(TestAdminEmail, Role.Admin, new ExceptionResult(x => x.Should().NotThrow())
)
.SetName("{m} (Admin => Admin)")
.SetDescription("User with Admin role adds another user with Admin role.");
}
}
No big issues with readability, besides, SetName() and SetDescription() methods in the test case source help with that.

How to resolve a Collection of Types from within the IoC Container

We're using MvvmCross in our app, and using the MvxSimpleIoCContainer
In the app startup, we register all of our Migrations.
it's easy do do since all migrations inherit from IMigration
typeof (IMigration)
.Assembly
.CreatableTypes()
.Inherits<IMigration>()
.AsTypes()
.RegisterAsLazySingleton();
After the migrations are registered, we need to run them consecutively, and therefore the MigrationRunner looks a little something like this.
Mvx.Resolve<IMigrationRunner>().RunAll(SystemRole.Client, new List<IMigration>
{
Mvx.IocConstruct<Migration001>(),
Mvx.IocConstruct<Migration002>()
});
as you can see, I'm explicitely constructing each Migration using Mvx. This get's tedious and is prone to mistakes when a bunch of migrations end up in the app.
What I'd prefer to be able to do is resolve the entire collection in one fell swoop, and not have to touch it every time I create a new Migration.
Is there a way to do this via MvvmCross?
Pseudo Code
Mvx.Resolve<IMigrationRunner>()
.RunAll(SystemRole.Client, Mvx.ResolveAll<IMigration>());
I would use LINQ to get the list of types. Unfortunately there's no way to get a list of registered types, so you'll have to enumerate the types again like you do for registration. You can even sort by type name. Now that you have a list of types, you can create a new list of instantiated/resolved types to pass into RunAll(). Something like:
var migrationTypes = typeof (IMigration)
.Assembly
.CreatableTypes()
.Inherits<IMigration>()
.AsTypes()
.OrderBy(t => t.Name)
.ToList();
Mvx.Resolve<IMigrationRunner>()
.RunAll(SystemRole.Client,
migrationTypes.Select(t => Mvx.Resolve(t)).ToList());
This is "browser" code, so no guarantees, but you get the gist.
Ok, so reflection is the answer to this problem for now, and eventually, I'd like to either extend our custom MvxServiceLocator : IServiceLocator to include something like
public IEnumerable<object> GetAllInstances(Type serviceType){...}
but for now I've just got a RunMigrations() method in the app
private void RunMigrations()
{
var migrationType = typeof (IMigration); // IMigration is in a separate assembly
var migrations = GetType().Assembly
.GetTypes()
.Where(
t => migrationType.IsAssignableFrom(t) && !t.IsAbstract)
.OrderBy(t => t.Name)
.Select(m => _serviceLocator.GetInstance(m) as IMigration)
.ToList();
var migrationRunner = new MigrationRunner(Mvx.Resolve<IDbProvider>());
migrationRunner.RunAll(SystemRole.Client, migrations);
}
where _serviceLocator.GetInstance(m) just lives in our custom MvxServiceLocator
public object GetInstance(Type serviceType)
{
return _ioCProvider.Resolve(serviceType);
}
Edit: here's how I extended our service locator wrapper.
public class MvxServiceLocator : IServiceLocator
{
private readonly IMvxIoCProvider _ioCProvider;
public MvxServiceLocator(IMvxIoCProvider ioCProvider)
{
_ioCProvider = ioCProvider;
}
public IEnumerable<TService> GetAllInstances<TService>()
{
var serviceType = typeof(TService);
var registrations = GetType().Assembly
.GetTypes()
.Where(
t => serviceType.IsAssignableFrom(t) && !t.IsAbstract)
.Select(m => (TService)_ioCProvider.Resolve(m));
return registrations;
}
}

How to implement custom SiteMapNodeProvider

I am trying to adapt the MvcSiteMapProvider to create the breadcrumb based on some Information stored in a database.
The answer in this post sounded promising so i implemented my own SiteMapNodeProvider. But then i didnt know how to wire things up so the newly implemented SiteMapNodeProvider is used instead of the static xml file ("Mvc.sitemap").
As i am using SimpleInjector in my project, i called the setup method in my already existent Injection-initialization code.
public static void Initialize()
{
Injection.Global = new Container();
InitializeContainer(Injection.Global);
Injection.Global.RegisterMvcControllers(Assembly.GetExecutingAssembly());
Injection.Global.RegisterMvcAttributeFilterProvider();
Injection.Global.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(Injection.Global));
}
private static void InitializeContainer(Container container)
{
// Setup configuration of DI
MvcSiteMapProviderContainerInitializer.SetUp(container);
//... register some other stuff for my project here ...
}
The MvcSiteMapProviderContainerInitializer class got created by the package: 'Mvcsitemapprovider.mvc4.di.simpleinjector/4.4.5'
Does anybody know what to do to make my project use the newly created SiteMapNodeProvider?
I couldnt find any documentation about this in the official docu...
edit: i tried what you suggested (even removed the old DI stuff and only used the one from the nuget-package) but still i am getting errors...
here is what i have in my MvcSiteMapProviderContainerInitializer
public static void SetUp(Container container)
{
bool securityTrimmingEnabled = false;
bool enableLocalization = true;
string absoluteFileName = HostingEnvironment.MapPath("~/Mvc.sitemap");
TimeSpan absoluteCacheExpiration = TimeSpan.FromMinutes(5);
string[] includeAssembliesForScan = new string[] { "testsitemap" };
// Extension to allow resolution of arrays by GetAllInstances (natively based on IEnumerable).
// source from: https://simpleinjector.codeplex.com/wikipage?title=CollectionRegistrationExtensions
AllowToResolveArraysAndLists(container);
var currentAssembly = typeof(MvcSiteMapProviderContainerInitializer).Assembly;
var siteMapProviderAssembly = typeof(SiteMaps).Assembly;
var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly };
var excludeTypes = new Type[]
{
typeof (SiteMapNodeVisibilityProviderStrategy),
typeof (SiteMapXmlReservedAttributeNameProvider),
typeof (SiteMapBuilderSetStrategy),
typeof (ControllerTypeResolverFactory),
// Added 2013-06-28 by eric-b to avoid default singleton registration:
typeof(XmlSiteMapController),
// Added 2013-06-28 by eric-b for SimpleInjector.Verify method:
typeof(PreservedRouteParameterCollection),
typeof(MvcResolver),
typeof(MvcSiteMapProvider.SiteMap),
typeof(MetaRobotsValueCollection),
typeof(RoleCollection),
typeof(SiteMapPluginProvider),
typeof(ControllerTypeResolver),
typeof(RouteValueDictionary),
typeof(AttributeDictionary)
,typeof(SiteMapNodeCreator)
};
var multipleImplementationTypes = new Type[]
{
typeof (ISiteMapNodeUrlResolver),
typeof (ISiteMapNodeVisibilityProvider),
typeof (IDynamicNodeProvider)
};
// Single implementations of interface with matching name (minus the "I").
CommonConventions.RegisterDefaultConventions(
(interfaceType, implementationType) => container.RegisterSingle(interfaceType, implementationType),
new Assembly[] { siteMapProviderAssembly },
allAssemblies,
excludeTypes,
string.Empty);
// Multiple implementations of strategy based extension points
CommonConventions.RegisterAllImplementationsOfInterfaceSingle(
(interfaceType, implementationTypes) => container.RegisterAll(interfaceType, implementationTypes),
multipleImplementationTypes,
allAssemblies,
new Type[0],
"^Composite");
container.Register<XmlSiteMapController>();
// Visibility Providers
container.RegisterSingle<ISiteMapNodeVisibilityProviderStrategy>(() =>
new SiteMapNodeVisibilityProviderStrategy(
container.GetAllInstances
<ISiteMapNodeVisibilityProvider>().
ToArray(), string.Empty));
// Pass in the global controllerBuilder reference
container.RegisterSingle<ControllerBuilder>(() => ControllerBuilder.Current);
container.RegisterSingle<IControllerBuilder, ControllerBuilderAdaptor>();
container.RegisterSingle<IBuildManager, BuildManagerAdaptor>();
container.RegisterSingle<IControllerTypeResolverFactory>(() =>
new ControllerTypeResolverFactory(new string[0],
container.GetInstance
<IControllerBuilder
>(),
container.GetInstance
<IBuildManager>()));
// Configure Security
container.RegisterAll<IAclModule>(typeof(AuthorizeAttributeAclModule), typeof(XmlRolesAclModule));
container.RegisterSingle<IAclModule>(() => new CompositeAclModule(container.GetAllInstances<IAclModule>().ToArray()));
// Setup cache
container.RegisterSingle<System.Runtime.Caching.ObjectCache>(() => System.Runtime.Caching.MemoryCache.Default);
container.RegisterSingleOpenGeneric(typeof(ICacheProvider<>), typeof(RuntimeCacheProvider<>));
container.RegisterSingle<ICacheDependency>(() => new RuntimeFileCacheDependency(absoluteFileName));
container.RegisterSingle<ICacheDetails>(() => new CacheDetails(absoluteCacheExpiration, TimeSpan.MinValue, container.GetInstance<ICacheDependency>()));
// Configure the visitors
container.RegisterSingle<ISiteMapNodeVisitor, UrlResolvingSiteMapNodeVisitor>();
// Prepare for the sitemap node providers
container.RegisterSingle<ISiteMapXmlReservedAttributeNameProvider>(
() => new SiteMapXmlReservedAttributeNameProvider(new string[0]));
container.RegisterSingle<IXmlSource>(() => new FileXmlSource(absoluteFileName));
// Register the sitemap node providers
container.RegisterSingle<XmlSiteMapNodeProvider>(() => container.GetInstance<XmlSiteMapNodeProviderFactory>()
.Create(container.GetInstance<IXmlSource>()));
container.RegisterSingle<ReflectionSiteMapNodeProvider>(() => container.GetInstance<ReflectionSiteMapNodeProviderFactory>()
.Create(includeAssembliesForScan));
// Register your custom sitemap node provider
container.RegisterSingle<ISiteMapNodeProvider, CustomSiteMapNodeProvider>();
// Register the collection of sitemap node providers (including the custom one)
container.RegisterSingle<ISiteMapBuilder>(() => container.GetInstance<SiteMapBuilderFactory>()
.Create(new CompositeSiteMapNodeProvider(
container.GetInstance<XmlSiteMapNodeProvider>(),
container.GetInstance<ReflectionSiteMapNodeProvider>(),
container.GetInstance<CustomSiteMapNodeProvider>())));
container.RegisterAll<ISiteMapBuilderSet>(ResolveISiteMapBuilderSets(container, securityTrimmingEnabled, enableLocalization));
container.RegisterSingle<ISiteMapBuilderSetStrategy>(() => new SiteMapBuilderSetStrategy(container.GetAllInstances<ISiteMapBuilderSet>().ToArray()));
}
private static IEnumerable<ISiteMapBuilderSet> ResolveISiteMapBuilderSets(Container container, bool securityTrimmingEnabled, bool enableLocalization)
{
yield return new SiteMapBuilderSet(
"default",
securityTrimmingEnabled,
enableLocalization,
container.GetInstance<ISiteMapBuilder>(),
container.GetInstance<ICacheDetails>());
}
private static void AllowToResolveArraysAndLists(Container container)
{
container.ResolveUnregisteredType += (sender, e) =>
{
var serviceType = e.UnregisteredServiceType;
if (serviceType.IsArray)
{
RegisterArrayResolver(e, container,
serviceType.GetElementType());
}
else if (serviceType.IsGenericType &&
serviceType.GetGenericTypeDefinition() == typeof(IList<>))
{
RegisterArrayResolver(e, container,
serviceType.GetGenericArguments()[0]);
}
};
}
private static void RegisterArrayResolver(UnregisteredTypeEventArgs e, Container container, Type elementType)
{
var producer = container.GetRegistration(typeof(IEnumerable<>)
.MakeGenericType(elementType));
var enumerableExpression = producer.BuildExpression();
var arrayMethod = typeof(Enumerable).GetMethod("ToArray")
.MakeGenericMethod(elementType);
var arrayExpression = Expression.Call(arrayMethod, enumerableExpression);
e.Register(arrayExpression);
}
}
but still i get the following exception:
No registration for type DynamicSiteMapNodeBuilder could be found and
an implicit registration could not be made. The constructor of the
type DynamicSiteMapNodeBuilder contains the parameter of type
ISiteMapNodeCreator with name 'siteMapNodeCreator' that is not
registered. Please ensure ISiteMapNodeCreator is registered in the
container, or change the constructor of DynamicSiteMapNodeBuilder.
First of all, to integrate with an existing DI setup, you should install MvcSiteMapProvider.MVC4.DI.SimpleInjector.Modules instead of MvcSiteMapProvider.MVC4.DI.SimpleInjector. You can downgrade by running this command from package manager console:
PM> Uninstall-Package -Id MvcSiteMapProvider.MVC4.DI.SimpleInjector
Be sure NOT to uninstall any dependencies. This will ensure that you don't have 2 sets of DI initialization code in your project - there should only be 1 for the entire application.
Next, you need to wire up for DI as well as some other initialization code required by MvcSiteMapProvider. The readme file contains instructions how to do this. Here is how you would do it with your existing configuration.
public static void Initialize()
{
Injection.Global = new Container();
InitializeContainer(Injection.Global);
Injection.Global.RegisterMvcControllers(Assembly.GetExecutingAssembly());
Injection.Global.RegisterMvcAttributeFilterProvider();
Injection.Global.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(Injection.Global));
}
private static void InitializeContainer(Container container)
{
// Setup configuration of DI (required)
MvcSiteMapProviderContainerInitializer.SetUp(container);
// Setup global sitemap loader (required)
MvcSiteMapProvider.SiteMaps.Loader = container.GetInstance<ISiteMapLoader>();
// Check all configured .sitemap files to ensure they follow the XSD for MvcSiteMapProvider (optional)
var validator = container.GetInstance<ISiteMapXmlValidator>();
validator.ValidateXml(HostingEnvironment.MapPath("~/Mvc.sitemap"));
// Register the Sitemaps routes for search engines (optional)
XmlSiteMapController.RegisterRoutes(RouteTable.Routes); // NOTE: You can put this in your RouteConfig.cs file if desired.
//... register some other stuff for your project here ...
}
If the /sitemap.xml endpoint doesn't work, you may also need to add this line to register the XmlSiteMapController:
Injection.Global.RegisterMvcControllers(typeof(MvcSiteMapProvider.SiteMaps).Assembly);
To implement ISiteMapNodeProvider, there is an example here: MvcSiteMapProvider ISiteMapBuilder in conjunction with IDynamicNodeProvider.
To register your custom ISiteMapNodeProvider, you just need to ensure it gets added to the constructor of SiteMapBuilder. You can also exclude the existing SiteMapNodeProviders from the code below depending on your needs.
// Register the sitemap node providers
container.RegisterSingle<XmlSiteMapNodeProvider>(() => container.GetInstance<XmlSiteMapNodeProviderFactory>()
.Create(container.GetInstance<IXmlSource>()));
container.RegisterSingle<ReflectionSiteMapNodeProvider>(() => container.GetInstance<ReflectionSiteMapNodeProviderFactory>()
.Create(includeAssembliesForScan));
// Register your custom sitemap node provider
container.RegisterSingle<ISiteMapNodeProvider, CustomSiteMapNodeProvider>();
// Register the collection of sitemap node providers (including the custom one)
container.RegisterSingle<ISiteMapBuilder>(() => container.GetInstance<SiteMapBuilderFactory>()
.Create(new CompositeSiteMapNodeProvider(
container.GetInstance<XmlSiteMapNodeProvider>(),
container.GetInstance<ReflectionSiteMapNodeProvider>(),
container.GetInstance<CustomSiteMapNodeProvider>())));
Do note that IDynamicNodeProvider (which is documented) does almost exactly the same thing as ISiteMapNodeProvider, so you could use that option instead. There are 3 main differences:
With IDynamicNodeProvider, you must create a "template" node that defines the dynamicNodeProvider attribute, and the template node itself won't be included in the SiteMap, so it must be used in conjunction with a ISiteMapNodeProvider implementation that processes the dynamic nodes (the built-in ISiteMapNodeProviders do this automatically).
IDynamicNodeProvider doesn't need to be part of the DI setup because it is already processed by both XmlSiteMapNodeProvider and ReflectionSiteMapNodeProvider.
With ISiteMapNodeProvider, you are working directly with the ISiteMapNode object, with IDynamicNodeProvider you are working with an abstraction (DynamicNodeProvider) and there is a conversion that happens automatically.
About SimpleInjector.Verify
If you want Verify() to work, you need to add the following to the excludeTypes array in the MvcSiteMapProviderContainerInitializer.
typeof(SiteMapNodeCreator),
typeof(DynamicSiteMapNodeBuilder)
I have added them to the module and will be in the next version of the Nuget package, but these modules do not update so you have to do it manually.
Note that the Verify() method tries to create an instance of everything that is registered with the container - including objects that never get created by the container in the real world. Therefore, if you use the Verify() method you have to be more diligent that something is not accidentally registered. This makes convention-based registration more difficult to do.