Simple Injector 4.1: Overriding Parameter Injection Behavior - c#

I'm new to using DI in C# and had a look at Windsor, Ninject, Autofac, Unity and Simple Injector. I originally discarded Simple Injector because I needed value-type injection (fx. connection strings) but found a blogpost describing this: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=94. Unfortunately the blogpost is outdated since IDependencyInjectionBehavior.BuildExpression is deprecated in version 4.1 and IDependencyInjectionBehavior.GetInstanceProducer has been introduced instead.
I'm not sure how to do what the blogpost describes with the new InstanceProducer. InstanceProducer has a static method FromExpression but I'm not sure which type etc. should be used.
I currently do Pure DI and have the following settings:
// Settings
var conLocal = ConfigurationManager.ConnectionStrings["APIPortMan"].ConnectionString;
var con = ConfigurationManager.ConnectionStrings["PortMan"].ConnectionString;
var conAzure = ConfigurationManager.ConnectionStrings["Azure"].ConnectionString;
var conSitecore = ConfigurationManager.ConnectionStrings["Sitecore"].ConnectionString;
var azureStorageAccount = ConfigurationManager.AppSettings.Get("StorageConnection");
var reportUploadPath = ConfigurationManager.AppSettings.Get("ReportUploadPath");
var PfsmlPath = ConfigurationManager.AppSettings.Get("PfsmlPath");
var reloadCounter = int.Parse(ConfigurationManager.AppSettings.Get("transactionServiceReloadCounter"));
var systemStartDate = DateTime.Parse(ConfigurationManager.AppSettings.Get("holdingServiceStartDate"));
var semaphoreCount = int.Parse(ConfigurationManager.AppSettings.Get("semaphoreCount"));
Then I have some repositories consuming these settings:
// Repositories
var _accountRepository = new AccountRepository(con, conAzure);
var _aggregatedPortfolioRelationshipRepository = new AggregatedPortfolioRelationshipRepository(conAzure);
var _aggregatedClientRelationshipRepository = new AggregatedClientRelationshipRepository(con, conAzure);
var _assetBondRepository = new AssetBondRepository(con, conAzure);
var _assetClassRepository = new AssetClassRepository(con, conAzure);
var _assetDerivativeRepository = new AssetDerivativeRepository(con, conAzure);
var _assetRepository = new AssetRepository(con, conAzure);
var _benchmarkRepository = new BenchmarkRepository(con, conAzure);
var _benchmarkWeightRepository = new BenchmarkWeightRepository(con, conAzure);
var _clientRepository = new ClientRepository(con, conAzure);
var _defaultPriceRepository = new DefaultPriceRepository(con, conAzure);
var _emailRepository = new UpdateEmailOutput(conAzure);
var _exchangeRateRepository = new ExchangeRateRepository(con, conAzure);
var _failedHoldingRepository = new FailedHoldingRepository(conLocal);
var _GICSRepository = new GICSRepository(con, conAzure);
var _holdingRepository = new HoldingRepository(conAzure);
var _limitLineRepository = new LimitLineRepository(con, conAzure);
var _PFSMLRepository = new PFSMLRepository(PfsmlPath);
var _portfolioRepository = new PortfolioRepository(con, conAzure);
var _sitecoreReportRepository = new SitecoreReportRepository(conSitecore, reportUploadPath);
var _systemInfoRepository = new SystemInfoRepository(conAzure);
var _transactionRepository = new TransactionRepository(con, conAzure);
And later some services consuming the repositories and a few of the settings.
Since most of the repositories share a common interface IRepository<T> (besides an individual interface like IAssetRepository that extends IRepository with the type Asset) I would like to be able to use batch creation /auto-wiring. Also I would like to avoid changing the DI setup every time a change is made to my main code (ie. new constructor parameter, new repository interface/class etc.). Also I would like to avoid using lambda's, "new" and getInstance in the registration process because this will require changes to the DI setup whenever I change a constructor.
I have already adopted the convention mentioned in the blogpost (using AzureConnectionString, PortManConnectionString etc.) in the constructors. All I need to do now is make sure Simple Injector handles value type parameters according to the conventions :)

I posted the same question on Github (as Steven noted).
https://github.com/simpleinjector/SimpleInjector/blob/v4.0.x/src/SimpleInjector.CodeSamples/ParameterConventionExtensions.cs contains an updated version of the convention-based approach although Steven/dotnetjunkie convinced me to take a different approach (using settings-objects).

Related

Update to Nlog 5.0: Configuration not applied

While upgrading from NLog 4.7.15 to 5.0.1 I found this test in our code base:
[Test]
public void CustomLogFactoryShouldBehaveCompletelyIndependent()
{
var memoryTarget4 = new MemoryTarget();
memoryTarget4.Layout = "${level}|${logger}|${message}${exception}";
var memoryTarget5 = new MemoryTarget();
var customConfig = new LoggingConfiguration();
customConfig.AddTarget("UnitTestLogger4", memoryTarget4);
customConfig.LoggingRules.Add(new LoggingRule("UnitTestLogger4", LogLevel.Trace, memoryTarget4));
customConfig.AddTarget("UnitTestLogger2", memoryTarget5);
customConfig.LoggingRules.Add(new LoggingRule("UnitTestLogger2", LogLevel.Info, memoryTarget5));
using (var customFactory = new LogFactory(customConfig)) // <<-- This ctor is marked obsolete
{
// Log logger defined only in custom config
var logger4 = customFactory.GetLogger("UnitTestLogger4");
logger4.Trace("Test4");
Assert.That(memoryTarget4.Logs.Count, Is.EqualTo(1));
Assert.That(memoryTarget5.Logs, Is.Empty);
memoryTarget4.Logs.Clear();
//... More cases tested here
}
}
The test works fine both in the old and the new version, but only after I disable the deprecation warning CS0618 for the constructor LogFactory(LoggingConfiguration).
To work around this, I tried to use the suggested alternative LoggingConfiguration(LogFactory), which connects the factory and the configuration basically the other way round.
[Test]
public void CustomLogFactoryShouldBehaveCompletelyIndependent()
{
var memoryTarget4 = new MemoryTarget();
memoryTarget4.Layout = "${level}|${logger}|${message}${exception}";
var memoryTarget5 = new MemoryTarget();
using (var customFactory = new LogFactory())
{
var customConfig = new LoggingConfiguration(customFactory);
customConfig.AddTarget("UnitTestLogger4", memoryTarget4);
customConfig.LoggingRules.Add(new LoggingRule("UnitTestLogger4", LogLevel.Trace, memoryTarget4));
customConfig.AddTarget("UnitTestLogger2", memoryTarget5);
customConfig.LoggingRules.Add(new LoggingRule("UnitTestLogger2", LogLevel.Info, memoryTarget5));
// customFactory.ReconfigExistingLoggers(); // <<-- Adding this changes nothing
// Log logger defined only in custom config
var logger4 = customFactory.GetLogger("UnitTestLogger4");
logger4.Trace("Test4");
Assert.That(memoryTarget4.Logs.Count, Is.EqualTo(1)); // <<-- Fails here, nothing was added to memoryTarget4.
Assert.That(memoryTarget5.Logs, Is.Empty);
memoryTarget4.Logs.Clear();
}
}
At least that's what I think should be changed. But the test fails now. The configuration is not applied, as the target does not get any logs.
What did I miss? What's the correct way of replacing the deprecated LogFactory(LoggingConfiguration) constructor here?
The constructor new LogFactory(customConfig) became obsolete to ensure that the LoggingConfiguration.Factory-option had the expected value (Using the intended isolated LogFactory instead of the global static LogManager.Factory)
You can replace:
customFactory.ReconfigExistingLoggers();
With this so the configuration is activated:
customFactory.Configuration = customConfig;
Alternative you could do this:
using var customFactory = new NLog.LogFactory().Setup().LoadConfiguration(builder => {
var memoryTarget4 = new MemoryTarget();
memoryTarget4.Layout = "${level}|${logger}|${message}${exception}";
var memoryTarget5 = new MemoryTarget();
builder.Configuration.LoggingRules.Add(new LoggingRule("UnitTestLogger4", LogLevel.Trace, memoryTarget4));
builder.Configuration.LoggingRules.Add(new LoggingRule("UnitTestLogger2", LogLevel.Info, memoryTarget5)));
}).LogFactory;
var logger4 = customFactory.GetLogger("UnitTestLogger4");
logger4.Trace("Test4");
Assert.That(memoryTarget4.Logs.Count, Is.EqualTo(1));
Assert.That(memoryTarget5.Logs, Is.Empty);
memoryTarget4.Logs.Clear();

How to set ImageResizer configuration programmatically?

I want to set the configuration parameter clientcache.minutesprogrammatically but im struggling with the config design in ImageResizer.
My approach currently is:
var lWebConfigReader = new System.Xml.XmlTextReader(#"Web.config");
var lXmlDocument = new System.Xml.XmlDocument();
lXmlDocument.Load(lWebConfigReader);
var lResizerNode = lXmlDocument.SelectSingleNode("/configuration/resizer");
var lSection = new ImageResizer.ResizerSection(lResizerNode.OuterXml);
var lConfig = new ImageResizer.Configuration.Config(lSection);
int mins = lConfig.get("clientcache.minutes", -1);
...
ImageResizer.Configuration.Config.Current.setConfigXml(lConfig.getConfigXml());
It seems a bit hacky and also doesn't work as the ClientCache plugin doesn't sent the Expires header as it normally should when clientcache.minutes is set.
What could be the issue?
After some digging in the source code i found out that in this particular case you need to alter the global configuration object as the ClientCache plugin reads the parameter via Get() from it. So my current solution is:
// read a XML where a <resizer>...</resizer> is present, in this case a typical Web.config as mentioned in the ImageResizer docs
var lWebConfigReader = new System.Xml.XmlTextReader(#"Web.config");
var lXmlDocument = new System.Xml.XmlDocument();
lXmlDocument.Load(lWebConfigReader);
// read the resizer tag to a node
var lResizerNode = lXmlDocument.SelectSingleNode("/configuration/resizer");
// create a section from the node
var lSection = new ImageResizer.ResizerSection(lResizerNode.OuterXml);
// create a new config object from the section
var lConfig = new ImageResizer.Configuration.Config(lSection);
// override the global configugration with the newly created one
ImageResizer.Configuration.Config.Current.setConfigXml(lConfig.getConfigXml());
// test the Get() call used by the ClientCache plugin
int mins = ImageResizer.Configuration.Config.Current.get("clientcache.minutes", -1);
This code could be placed in a ICurrentConfigProvider implementation or Application_Start() in Global.asax.

Sabre Web Services .NET API Examples that aren't MVC?

This is pretty general, but I'm having a hell of a time figuring out how to consume some of the more complicated Sabre APIs.
I have built working .NET proxy classes in C# using the WSDL for the basic APIs (CreateSession, CloseSession) but for the more complicated APIs I have a really hard time parsing out the complicated XML schema to figure out which methods to call in my program.
Are there any other .NET resources/examples out there that aren't wrapped up in MVC like the code example that Sabre posted on GitHub?
I'm trying to figure out how to use APIs like OTA_AirPriceLLSRQ and TravelItineraryReadRQ.
Thanks in advance for any help!
As I mentioned on the comments, you should not focus on the actual MVC wrapping, as you'll be mainly putting stuff in the Model, or actually you'll put this somewhere else and consume it in the model.
Anyway, just for you to have as example, here's a VERY generic BFM (BargianFinderMax) class. With this approach it's required to create an instance, and after calling the Execute method it stores the response in the instance.
I hope it helps.
using BargainFinderMaxRQv310Srvc;
using System;
using System.IO;
namespace ServicesMethods
{
public class BFM_v310
{
private BargainFinderMaxService service;
private OTA_AirLowFareSearchRQ request;
public OTA_AirLowFareSearchRS response;
public BFM_v310(string token, string pcc, string convId, string endpoint)
{
//MessageHeader
MessageHeader mHeader = new MessageHeader();
PartyId[] pId = { new PartyId() };
pId[0].Value = "SWS";
From from = new From();
from.PartyId = pId;
To to = new To();
to.PartyId = pId;
mHeader.Action = "BargainFinderMaxRQ";
mHeader.Service = new Service()
{
Value = mHeader.Action
};
mHeader.ConversationId = convId;
mHeader.CPAId = pcc;
mHeader.From = from;
mHeader.To = to;
mHeader.MessageData = new MessageData()
{
Timestamp = DateTime.UtcNow.ToString()
};
//Security
Security security = new Security();
security.BinarySecurityToken = token;
//Service
service = new BargainFinderMaxService();
service.MessageHeaderValue = mHeader;
service.SecurityValue = security;
service.SoapVersion = System.Web.Services.Protocols.SoapProtocolVersion.Soap11;
service.Url = endpoint;
createRequest(pcc);
}
private void createRequest(string pcc)
{
request = new BargainFinderMaxRQv310Srvc.OTA_AirLowFareSearchRQ();
request.AvailableFlightsOnly = true;
request.Version = "3.1.0";
request.POS = new SourceType[1];
SourceType source = new SourceType();
source.PseudoCityCode = pcc;
source.RequestorID = new UniqueID_Type();
source.RequestorID.ID = "1";
source.RequestorID.Type = "1";
source.RequestorID.CompanyName = new CompanyNameType();
source.RequestorID.CompanyName.Code = "TN";
source.RequestorID.CompanyName.CodeContext = "Context";
request.POS[0] = source;
OTA_AirLowFareSearchRQOriginDestinationInformation originDestination = new OTA_AirLowFareSearchRQOriginDestinationInformation();
originDestination.OriginLocation = new OriginDestinationInformationTypeOriginLocation();
originDestination.OriginLocation.LocationCode = "BCN";
originDestination.DestinationLocation = new OriginDestinationInformationTypeDestinationLocation();
originDestination.DestinationLocation.LocationCode = "MAD";
originDestination.ItemElementName = ItemChoiceType.DepartureDateTime;
originDestination.Item = "2017-09-10T12:00:00";
originDestination.RPH = "1";
request.OriginDestinationInformation = new OTA_AirLowFareSearchRQOriginDestinationInformation[1] { originDestination };
request.TravelerInfoSummary = new TravelerInfoSummaryType()
{
AirTravelerAvail = new TravelerInformationType[1]
};
request.TravelerInfoSummary.AirTravelerAvail[0] = new TravelerInformationType()
{
PassengerTypeQuantity = new PassengerTypeQuantityType[1]
};
PassengerTypeQuantityType passenger = new PassengerTypeQuantityType()
{
Quantity = "1",
Code = "ADT"
};
request.TravelerInfoSummary.AirTravelerAvail[0].PassengerTypeQuantity[0] = passenger;
request.TravelerInfoSummary.PriceRequestInformation = new PriceRequestInformationType();
request.TravelerInfoSummary.PriceRequestInformation.CurrencyCode = "USD";
//PriceRequestInformationTypeNegotiatedFareCode nego = new PriceRequestInformationTypeNegotiatedFareCode();
//nego.Code = "ABC";
//request.TravelerInfoSummary.PriceRequestInformation.Items = new object[1] { nego };
request.TPA_Extensions = new OTA_AirLowFareSearchRQTPA_Extensions();
request.TPA_Extensions.IntelliSellTransaction = new TransactionType();
request.TPA_Extensions.IntelliSellTransaction.RequestType = new TransactionTypeRequestType();
request.TPA_Extensions.IntelliSellTransaction.RequestType.Name = "50ITIN";
}
public bool Execute()
{
response = service.BargainFinderMaxRQ(request);
return response.PricedItinCount > 0;
}
}
}
My advice is you should add separate models which are built based on Sabre models, and which flatten the whole structure.
For example, TravelItineraryReadRS is a quite complicated document. Using it properties in your program is a real "pain", because every time you need to remember the whole path that leads to to specific information (like, "what is passenger type for PersonName of NameNumber 01.01?").
I suggest you have dedicated model (let's name it Reservation), which have all information that you will need later in your application, extracted from TravelItineraryReadRs.
In order to achieve this you need a dedicated converter which will make convert TravelItineraryReadRs model into Reservation model. Now, inside Reservation model you could have list of Passenger models, which have in on place all important information (NameNumber, PassengerType, SSR codes, etc).
This improves readability and as a bonus you decouple your application from Sabre (imagine, one day someone asks "can we switch from Sabre to Amadeus?" - if you use dedicated models the answer is "yes". If you don't have, then the answer is "probably yes, but it will take 6-9 months).

C# Ninject to Structure map converter

In ninject I have code like this:
var resourceManagers = new ResourceManager[1];
resourceManagers[0] = Validation.ResourceManager;
kernel.Bind<ILocalizedStringProvider>().To<ResourceStringProvider>()
.WithConstructorArgument("resourceManager", resourceManagers);
kernel.Rebind<ModelValidatorProvider>().To<LocalizedModelValidatorProvider>();
I want to convert this to StructureMap
I did like this:
IContainer container = new Container();
var ioC = new IoC();
ioC.Initialize(container);
container.Configure(x =>
{
var resourceManagers = new ResourceManager[1];
resourceManagers[0] = ModelValidation.ResourceManager;
x.For<ILocalizedStringProvider>().Use<ResourceStringProvider>.Ctor<string>(#"resourceManager").Is(resourceManagers);
x.For<ModelValidatorProvider>().Add<LocalizedModelValidatorProvider>();
});
DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver = new StructureMapDependencyResolver(container);
but I get exception:
Severity Code Description Project File Line Suppression State
Error CS0119 'CreatePluginFamilyExpression.Use()'
is a method, which is not valid in the given context
How to do this with StructureMap?
Use () because is a method
var resourceManagers = new ResourceManager[1];
resourceManagers[0] = ModelValidation.ResourceManager;
x.For<ILocalizedStringProvider>().Use<ResourceStringProvider>().Ctor<ResourceManager[]> (#"resourceManager").Is(resourceManagers);
x.For<ModelValidatorProvider>().Add<LocalizedModelValidatorProvider>();
As the error message says, this:
.Use<ResourceStringProvider>
Should be:
.Use<ResourceStringProvider>()
Because it is a method.

How to create indexes in MongoDB via .NET

I've programmatically created a new document collection using the MongoDB C# driver.
At this point I want to create and build indexes programmatically. How can I do that?
Starting from v2.0 of the driver there's a new async-only API. The old API should no longer be used as it's a blocking facade over the new API and is deprecated.
The currently recommended way to create an index is by calling and awaiting CreateOneAsync with an IndexKeysDefinition you get by using Builders.IndexKeys:
static async Task CreateIndexAsync()
{
var client = new MongoClient();
var database = client.GetDatabase("HamsterSchool");
var collection = database.GetCollection<Hamster>("Hamsters");
var indexKeysDefinition = Builders<Hamster>.IndexKeys.Ascending(hamster => hamster.Name);
await collection.Indexes.CreateOneAsync(new CreateIndexModel<Hamster>(indexKeysDefinition));
}
you should use CreateIndex as EnsureIndex is marked obsolete for future compatibility with the next versions of MongoDB:
var client = new MongoClient("mongodb://localhost");
var db = client.GetServer().GetDatabase("db");
var collection = db.GetCollection<Hamster>("Hamsters");
collection.CreateIndex(IndexKeys<Hamster>.Ascending(_ => _.Name));
The overload of CreateOneAsync in the currently accepted answer is now marked as obsolete with the message "Use CreateOneAsync with a CreateIndexModel instead." Here's how you do it:
static async Task CreateIndex(string connectionString)
{
var client = new MongoClient(connectionString);
var database = client.GetDatabase("HamsterSchool");
var collection = database.GetCollection<Hamster>("Hamsters");
var indexOptions = new CreateIndexOptions();
var indexKeys = Builders<Hamster>.IndexKeys.Ascending(hamster => hamster.Name);
var indexModel = new CreateIndexModel<Hamster>(indexKeys, indexOptions);
await collection.Indexes.CreateOneAsync(indexModel);
}
Something like this should do:
var server = MongoServer.Create("mongodb://localhost");
var db = server.GetDatabase("myapp");
var users = db.GetCollection<User>("users");
users.EnsureIndex(new IndexKeysBuilder().Ascending("EmailAddress"));
Please see the following bits in the documentation:
http://api.mongodb.org/csharp/current/html/06bcd201-8844-3df5-d170-15f2b423675c.htm
There is an entire area on Indexing under the Definitions and Builders documentation page:
http://mongodb.github.io/mongo-csharp-driver/2.4/reference/driver/definitions/#index-keys
Example:
IndexKeysDefinition<MyModel> keys = "{ Reference: 1 }";
var indexModel = new CreateIndexModel<MyModel>(keys);
await _context.Indexes.CreateOneAsync(indexModel);
the easiest way to create indexes in c# is by using the driver wrapper library MongoDB.Entities. here's an example of creating a text index:
DB.Index<Author>()
.Key(a => a.Name, Type.Text)
.Key(a => a.Surname, Type.Text)
.Create();
and to do a full-text search, you simply do:
DB.SearchText<Author>("search term");
haven't seen anything else that makes it simpler than that.

Categories