While the concept of IoC isn't foreign to me, I'm new to Unity and I'm having trouble connecting the metaphorical dots, so to speak.
In our project we have a class library for logic, then several class libraries with repositories implementing common interfaces (they provide access and allow CRUD operations on different data sources). Finally, this is all used together in either an MVC web site or a SharePoint site.
We wanted to implement Unity to keep a single configuration and make the whole thing better.
But... there are some design issues I'm facing. For example - the MVC web site, as far as communicating with the underlying SQL database is concerned, can have everything it needs specified in the configuration section for the Unity container. However, said web site also needs to occasionally connect with SharePoint... in order to create a SharePoint repository, I need to create an SPWeb
object and pass it to the repository constructor.
I CAN do that using ParameterOverride
when resolving the type, but this doesn't... feel right. There are cases where a larger number of parameters need to overridden this way; to resolve a logic class I need to pass repositories, which in turn are resolved by passing a manually created SPWeb
object... When this happens it seems I'm no longer following an IoC pattern. The kicker here is that I cannot have the SPWeb
object defined in the Unity container!
Is there a pattern (a practical example would be superb) where Unity is used but certain values are being passed TO the container while resolving types, without the need to use ParameterOverride
?
EDIT
I feel I need to provide a more specific example of why ParameterOverride
is a problem. Below is a fragment of code used by a SharePoint timer job. The job in question synchronizes some data between SharePoint and SQL, so it requires two different repositories implementing the same interface. Because the code is ran from SharePoint, it cannot access a global configuration defined DataContext
(EntityFramework, code-first) because there's no web.config
file to read the connection string from. Also, due to the weirdness that is SharePoint, I cannot "get" the current SPWeb
object, and instead need to create it on the fly.
The end result is a ton on using
statements, and a lot of parameters need to overridden when using Resolve
. Also, the need to pass the parameter NAME as a string would make this really difficult to code for anyone else - this information isn't known from the interface alone.
public override void Execute(Guid targetInstanceId)
{
var siteUrl = Properties["Site"] as string;
var sqlConnection = Properties["SQL"] as string;
if (String.IsNullOrEmpty(siteUrl))
throw new ArgumentNullException("Site URL is missing.");
if (String.IsNullOrEmpty(sqlConnection))
throw new ArgumentNullException("SQL connection string is missing.");
using (var site = new SPSite(siteUrl, SPUserToken.SystemAccount))
using (var web = site.OpenWeb())
using (var spPollRepository = Configuration.ServiceLocator.SharePointContainer.Container.Resolve<IPollRepository>(new ParameterOverride("web", web)))
using (var ctx = Configuration.ServiceLocator.SharePointContainer.Container.Resolve<Repositories.PollsDataContext>(Configuration.ServiceLocator.SqlNamedRegistration, new ParameterOverride("nameOrConnectionString", sqlConnection), new ParameterOverride("initialize", false)))
using (var sqlPollRepository = Configuration.ServiceLocator.SharePointContainer.Container.Resolve<IPollRepository>(Configuration.ServiceLocator.SqlNamedRegistration, new ParameterOverride("ctx", ctx)))
{
IPollsSyncLogic logic = Configuration.ServiceLocator.SharePointContainer.Container.Resolve<Logic.IPollsSyncLogic>(new ParameterOverride("sqlRepository", sqlPollRepository), new ParameterOverride("spRepository", spPollRepository));
logic.SyncPolls();
}
}
There's a big chance I'm using Unity... wrong. Or that some other fundamental definition has been mixed up. I hope this clears up any confusion regarding the question.
1 Answer 1
If the entities returned by the SharePoint repository are specific, just create a [TheEntity]Repository
interface with a single implementation that talks to SharePoint.
If the [TheEntity]Repository
interface has two implementations that talk respectively to SharePoint and a SQL database, you could use ResolvedParameters at configuration time (not Overrides at resolve time) to inject the right concrete repository depending on the type that is being injected into.
Edit
Seeing the code you posted, the big flaw is that you would like to use IOC and abstract repositories, but this is ruined by the fact that IPollsSyncLogic
perfectly knows about Sql and Sharepoint, since its constructor has two parameters explicitly named sqlRepository and spRepository.
You should either
- Fully embrace the specificity of
IPollsSyncLogic
as an SP to SQL synchronizer by using explicit SQL and SP repository types in its constructor signature.
or
- Make
IPollsSyncLogic
a more abstract synchronizer between any two implementations of the same repository interface. In that case, just name its constructor parameters something likerepositorySource
andrepositoryTarget
. In your IoC configuration, use an InjectionFactory to specify the two concrete repository types.
As a side note, you should avoid resorting to Container.Resolve
and rely on automatic resolution based on configuration when possible, because discrete calls to the container are usually hidden dependencies. For dependencies that are part of using
statements and can't be injected through a constructor, you can rely on a Factory that produces them on the fly.
Regarding your problem with SharePoint accessing DataContext
, I think it deserves a separate question.
-
I... don't quite understand what you mean by
[TheEntity]Repository
. I code in C#, so this is rather confusing. Secondly, there's a common interface for performing CRUD operations on any given model object, and two implementations - one for SQL and another for SharePoint (in theory there could be more, but lets stick to this for now). Also,ResolvedParameters
won't solve the problem. As I wrote in a comment - SharePoint can be weird and sometimes in SharePoint I can useSPContext.Current
to get the currentSPWeb
, and in other places I cannot.MBender– MBender2014年11月20日 07:44:06 +00:00Commented Nov 20, 2014 at 7:44 -
By entity I mean whatever object returned by the repository. Could be
UserRepository
,ArticleRepository
, anything. But we may not have the same definition of repository. Why won'tResolvedParameters
solve the problem ? What does this have to do withSPContext
? I guess without some code to explain it, the question is a bit blurry.guillaume31– guillaume312014年11月20日 08:11:06 +00:00Commented Nov 20, 2014 at 8:11 -
Agreed. I added some sample code to my question.MBender– MBender2014年11月20日 10:02:18 +00:00Commented Nov 20, 2014 at 10:02
-
It's much clearer with code :) See my edit.guillaume31– guillaume312014年11月20日 12:42:36 +00:00Commented Nov 20, 2014 at 12:42
-
OK, fair point about the parameter names of the Logic class, but it IS generic. ;) I used the specific names because it was easier, during writing, to remember which is which. So... An
InjectorFactory
then. I... uhm... haven't used that yet. Well, at least I know what to look for! :)MBender– MBender2014年11月20日 12:45:20 +00:00Commented Nov 20, 2014 at 12:45
SPWeb
because in these casesSPContext.Current
is unavailable...