We are changing the architecture of a legacy application to use Spring JDBC.
The application needs to call two stored procedures in a row within one transaction. Stored Procedure A registers a security token that is required to run stored procedure B:
BEGIN package_a.a('token'); END;
BEGIN package_b.b('foo','bar','baz'); END;
I've created Spring Stored Procedures for each of these:
#Component
public class SecurityTokenSP extends StoredProcedure {
#Autowired
public SecurityTokenSP(DataSource ds) {
super(ds, "package_a.a");
declareParameter(new SqlParameter("token", Types.VARCHAR));
compile();
}
public void setToken(String token) {
Map<String, Object> inputParams = new HashMap<String, Object>();
inputParams.put("token", token);
execute(inputParams);
}
}
// Similar code for procedure B (FooBarBazSP)
I call both of these from a Service within one method:
#Service
public class MyService {
#Autowired
private SecurityTokenSP securityTokenSP;
#Autowired
private FooBarBazSP fooBarBazSP;
#Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
public void doIt(String foo, String bar, String baz) {
securityTokenSP.setToken("myToken");
fooBarBazSP.doIt(foo, bar, baz);
}
}
Everything is wired correctly and each procedure is called. However the second procedure is acting as if the first procedure was not run. I suspect that Spring is calling 'commit' on the connection during the execute call, but that is just a theory.
I don't see how I can combine the two classes into one as the StoredProcedure constructor takes the name of a particular stored procedure and seems to be designed to just wrap one stored procedure.
Related
I have a really nasty StackOverflowException in my spring backend, that I need help with. This is not going to be solved easily. I really hope to find some help here.
Most parts of my backend work. I can query my REST interface for models, they are nicely returned by spring-hateoas, GET, PUT and POST operations work. But one exception: When I try to update an existing DelegationModel, then I run into an endless StackOverflowException.
Here is my DelegetionModel.java class. Please mark, that delegation model actually doesn't have any property annotated with #CreatedBy!
#Entity
#Data
#NoArgsConstructor
#RequiredArgsConstructor(suppressConstructorProperties = true) //BUGFIX: https://jira.spring.io/browse/DATAREST-884
#EntityListeners(AuditingEntityListener.class) // this is necessary so that UpdatedAt and CreatedAt are handled.
#Table(name = "delegations")
public class DelegationModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
/** Area that this delegation is in */
#NonNull
#NotNull
#ManyToOne
public AreaModel area;
/** reference to delegee that delegated his vote */
#NonNull
#NotNull
#ManyToOne
public UserModel fromUser;
/** reference to proxy that receives the delegation */
#NonNull
#NotNull
#ManyToOne
public UserModel toProxy;
#CreatedDate
#NotNull
public Date createdAt = new Date();
#LastModifiedDate
#NotNull
public Date updatedAt = new Date();
}
As described in the Spring-data-jpa doc I implemented the necessary AuditorAware interface, which loads the UserModel from the SQL DB. I would have expected that this AuditorAware interface is only called for models that have a field annotated with #CreatedBy.
#Component
public class LiquidoAuditorAware implements AuditorAware<UserModel> {
Logger log = LoggerFactory.getLogger(this.getClass()); // Simple Logging Facade 4 Java
#Autowired
UserRepo userRepo;
#Override
public UserModel getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
log.warn("Cannot getCurrentAuditor. No one is currently authenticated");
return null;
}
User principal = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
UserModel currentlyLoggedInUser = userRepo.findByEmail(principal.getUsername()); // <<<<======= (!)
return currentlyLoggedInUser;
} catch (Exception e) {
log.error("Cannot getCurrentAuditor: "+e);
return null;
}
}
}
Now I update a DelegationModel in my UserRestController. The functional "Scrum User Story" here is:
As a user I want to be able to store a delegation so that I can forward my right to vote to my proxy.
#RestController
#RequestMapping("/liquido/v2/users")
public class UserRestController {
[...]
#RequestMapping(value = "/saveProxy", method = PUT, consumes="application/json")
#ResponseStatus(HttpStatus.CREATED)
public #ResponseBody String saveProxy(
#RequestBody Resource<DelegationModel> delegationResource,
//PersistentEntityResourceAssembler resourceAssembler,
Principal principal) throws BindException
{
[...]
DelegationModel result = delegationRepo.save(existingDelegation);
[...]
}
[...]
}
For some reason, that I cannot see, this actualy calls the AuditorAware implementation above. The problem is now, that my LqiuidoAuditorAware implementation is called again and again in and endless loop. It seems that the query for the UserModel inside LiquidoAuditorAware.java calls the LiquidoAuditorAware again. (Which is unusual, because that is only a read operation from the DB.)
Here is the full ThreadDump as a Gist
All the code can by found in this github repo
I'd really apriciate any help here. I am searching in the dark :-)
The reason for the behavior you see is that the AuditorAware implementation is called from within a JPA #PrePersist/#PreUpdate callback. You now issue a query by calling findByEmail(…), which triggers the dirty-detection again, which in turn causes the flushing to be triggered and thus the invocation of the callbacks.
The recommended workaround is to keep an instance of the UserModel inside the Spring Security User implementation (by looking it up when the UserDetailsService looks up the instance on authentication), so that you don't need an extra database query.
Another (less recommended) workaround could be to inject an EntityManager into the AuditorAware implementation, call setFlushMode(FlushModeType.COMMIT) before the query execution and reset it to FlushModeType.AUTO after that, so that the flush will not be triggered for the query execution.
Here are the classes and interfaces I'm dealing with.
public interface IAccountService
{
//omitted for brevity...
}
public abstract class UserClientBase<T> : ClientBase<T> where T : class
{
protected UserClientBase(Binding binding, EndpointAddress remoteAddress, string userName, string password)
: base(binding, remoteAddress)
{
if (ClientCredentials == null) return;
ClientCredentials.UserName.UserName = userName;
ClientCredentials.UserName.Password = password;
}
}
public class AccountProxy : UserClientBase<IAccountService>, IAccountService
{
private static readonly WSHttpBinding Binding = WcfHelpers.ConfigureWsHttpBinding();
private static readonly EndpointAddress EndpointAddress =
new EndpointAddress(#"https://server.project.local/Project/Account/AccountService.svc");
public AccountProxy(string userName, string password)
: base(Binding, EndpointAddress, userName, password)
{
}
//omitted for brevity
}
public interface ISafeClient<out TService> : IDisposable where TService : class
{
//omitted for brevity...
}
public class SafeClient<TClient, TService> : ISafeClient<TService>
where TClient : UserClientBase<TService>, TService
where TService : class
{
//omitted for brevity...
}
public AccountController(ISafeClient<IAccountService> accountProxy)
{
_accountProxy = accountProxy;
}
I'm using Asp.net MVC 5 with Autofac integration, here are the registrations in my Global.asax Application_Start method.
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterType<AccountProxy>().As<IAccountService>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(SafeClient<,>)).As(typeof(ISafeClient<>)).InstancePerLifetimeScope();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
Now when I try to browse to a page handled by the AccountController, I get the following exception in the URL.
None of the constructors found with
'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type
'MyMvcApp.Controllers.AccountController' can be invoked with the
available services and parameters: Cannot resolve parameter
'Project.Client.Proxies.ISafeClient`1[Project.Client.Contracts.Service.IAccountService]
accountProxy' of constructor 'Void
I'm not sure what I'm missing here. When I look in the container's ComponentRegistry at runtime I see the Controllers and the AccountProxy. Thanks in advance.
Update:
I can inject an IAccountService successfully if I register it like this instead:
builder.Register(a => new AccountProxy("userName", "password")).As<IAccountService>();
However when I try to inject the ISafeClient I still get an exception. I think I have a larger problem here. Essentially, I'm trying to wrap a ClientBase to include safe disposing and retry logic. It handles communication and message exceptions, faulted channel states, etc. Every time I execute a method on my SafeClient, the ClientBase is re-instantiated, then automatically disposed of. If this is the case, should I even be injecting it? Ugghh.. I'm confused.
Update 2:
Looks like I can just register like this.
builder.RegisterType<SafeClient<AccountProxy, IAccountService>>().As<ISafeClient<IAccountService>>().InstancePerLifetimeScope();
Then my AccountController changes to this:
private readonly ISafeClient<IAccountService> _accountProxy;
public AccountController(ISafeClient<IAccountService> accountProxy)
{
_accountProxy = accountProxy;
}
Is there any downside to doing it this way, other than the obvious which is having to register teach safe client individually?
In C#, MVC code first application I have
public class CarContext : DbContext { }
class in first version of application. And connection string is like
<add name="CarContext" providerName="System.Data.SqlClient" Integrated Security=true;
connectionString="Data Source=Dragon; Initial Catalog=CarDBv1;"/>
When I run application, first version of database is created - CarDBv1.
Then I edit my CarContext class, for example, add new table, change any property etc., also change version of application, change connection string
Initial Catalog=CarDBv1; to Initial Catalog=CarDBv2; and run project. In this case I have 2 database: CarDBv1 and CarDBv2. But, CarContext class is same in applications.
Now, I need to connect both database and their context(CarContext) from any console application and use their tables for converting, reading etc.
I found a similar answer here: https://stackoverflow.com/a/16860878/1534785
But in my applications context name is same.
How can I create 2 instances for every CarContext in applications by their database connection string?
You can use an overloaded constructor to DbContext to allow contexts to point at and arbitrary database which is NOT declared in app.config.
See the constructor with dbConnection.
public class MyDbContext : DbContext, IContextOptions {
//ctors
protected BosBaseDbContext(string connectionName)
: base(connectionName) { }
protected BosBaseDbContext(DbConnection dbConnection, bool contextOwnsConnection)
: base(dbConnection, contextOwnsConnection) { }
}
usage
//datasource could be localhost, DBName the catalog name
new MyDbContext((GetSqlConn4DbName(dataSource,dbName )),true);
public DbConnection GetSqlConn4DbName(string dataSource, string dbName) {
var sqlConnStringBuilder = new SqlConnectionStringBuilder();
sqlConnStringBuilder.DataSource = String.IsNullOrEmpty(dataSource) ? DefaultDataSource : dataSource;
sqlConnStringBuilder.IntegratedSecurity = true;
sqlConnStringBuilder.MultipleActiveResultSets = true;
var sqlConnFact = new SqlConnectionFactory(sqlConnStringBuilder.ConnectionString);
var sqlConn = sqlConnFact.CreateConnection(dbName);
return sqlConn;
}
I am wondering what is the best way to use NLog with Managed Extensibility Framework (MEF)?
I have an application that support plugins using MEF architecture (Import and Exports etc)
I want to add logging capability to my application.
As a logging component I want to use NLog.
What would you recommend?
1. Create a wrapper for NLog, i.e. additional plugin that configures NLog and exports functions like void Log(string level, string message) that other plugins importing
2. Every plugin should have it is own instance of NLog configured and used. (They all would write to the same file actually).
This is an interesting approach, however, it seems to suffer from the drawback that all loggers that are injected (or the one singleton that is injected) will be the same instance (or will have the same name, the name being the name of the NLogLoggingService class. That means that you cannot very easily control the granularity of logging (i.e. turn logging to "Info" level in one class and "Warn" in another class). Also, if you opt to use the call site formatting tokens, you will always get the call site of the call the the NLog logger rather than the call site in your application code.
Here is an abbreviated version of the logger that was linked:
[Export(Services.Logging.LoggingService, typeof(ILoggingService))]
class NLogLoggingService : ILoggingService
{
Logger log; public NLogLoggingService()
{
log = LogManager.GetCurrentClassLogger();
}
public void Debug(object message)
{
log.Debug(message);
}
public void DebugWithFormat(string format, params object[] args)
{
if (args.Length == 0)
{
log.Debug(format);
}
else
{
Debug(string.Format(format, args));
}
}
public bool IsDebugEnabled
{
get
{
return log.IsDebugEnabled;
}
}
}
In the constructor LogManager.GetCurrentClassLogger() is used to get the NLog logger. GetCurrentClassLogger will return a NLog logger that is "named" based on the "current" type, which, in this case, is NLogLoggingService. So, to configure NLog in the app.config file, you will configure based on the that the logger is named "SoapBox.Core.NLogLoggingService". Commonly, in code that uses NLog (or log4net) directly, each class gets its own uniquely named logger like this:
namespace MyNamespace
{
public class MyClass1
{
private static readonly Logger logger LogManager.GetCurrentClassLogger();
public void DoSomeWork()
{
logger.Info("Logging from inside MyClass1.DoSomeWork");
}
}
public class MyClass2
{
private static readonly Logger logger LogManager.GetCurrentClassLogger();
public void DoSomeWork()
{
logger.Info("Logging from inside MyClass2.DoSomeWork");
}
}
}
Now the logging for MyClass1 and MyClass2 is individually controllable. You can configure different levels for each class, send them to different targets, or turn one or both off altogether. Alternatively, due to the concept of logger hierarchies in both log4net and NLog, you could control the logging in both class simultaneously by configuring a "logger" for the namespace (MyNamespace in this case), or any "ancestor" namespace. If there is not a logger configured for the fully qualified typename, then the logging framework essentially moves up the hierarchy by considering the name a dot delimited string and removing the last chunk and checking to see if that logger is configured. So, in this case, we are requesting loggers for MyNamespace.MyClass1 and MyNamespace.MyClass2. I could configure the app.config file to have MyNamespace log at the "info" and write to a file target (appender in log4net-speak). If I did that, then both loggers that I requested via their fully qualified names would inherit the MyNamespace configuration.
With the suggested way of injecting NLog via MEF, you will only have one logger instance, so you cannot configure each class to log differently. Also, as I mentioned earlier, if you opt to log call site information, you will always get "SoapBox.Core.NLogLoggingService" for the class and "Debug" (or DebugWithFormat, or Info, or InfoWithFormat, etc) for the method.
This seems to be an issue with successfully injecting loggers from log4net and NLog. You can see the question that I asked about this very issue a couple of months ago.
Ultimately I was able to figure out how some dependency injection frameworks can successfully inject log4net and NLog loggers that are specific to the class being created (i.e. if the DI framework is instantiating MyClass, which in turn depends on an ILogger interface, then MyClass will get a logger that is essentially equivalent to what would have happened if MyClass requested the logger itself via the LogManager.GetCurrentClassLogger api). Generally "resolvers" in DI/IoC frameworks are given the current context (containing, among other information, the type of the object currently being created). With that type available, it becomes a simple matter of having a logging framework-specific resolver receive that type and pass it along to the logging framework to create a logger appropriate for that type.
In order to get the most out of NLog's (and log4net's) capabilities you would really like to be able to tell MEF that your class is dependendent on "ILogger", but also that the instance of "ILogger" that gets injected into your class should depend on the Type of your class.
I don't know how easy it will be to achieve that with MEF. Alternatively, you could wrap NLog's static LogManager in a ILogManager and inject that. That would deviate from the normal "inject ILogger" paradigm.
To summarize: If you inject NLog via MEF this way, you will indeed be able to log with NLog, but you will only ever have one named logger (SoapBox.Core.NLogLoggingService). This means that you will not be able control with any degree of granularity - either for levels/on/off or for output (NLog Target/log4net Appender)
I don't have a good answer for what to do as far as injecting NLog via MEF AND keeping the granularity/flexibility that "raw" NLog gives you.
I can say that we have decided to use Common.Logging for .NET to abstract the logging framework but we decided NOT to inject logging. Instead, we will just use a static LogManager (as provided by Common.Logging) to hand out loggers.
I think Option 1 is better.
You can take a look at how the open source framework SoapBox Core imports a reference to an ILoggingService using MEF. It also provides a default implementation of the logging service based on NLog, but you could easily swap it out for log4Net, for example.
For reference:
the ILoggingService interface
the Logging Service that wraps NLog and exports itself using MEF
SoapBox Core is LGPL'd, so you might be able to use (this part) in your application.
I have been fighting with this problem a while now.
Really improtant was the Callsite (FullyQualified Namespace) within the logfiles.
First, i tryed to get the right logger out of the Stacktrace:
[MethodImpl(MethodImplOptions.NoInlining)]
private static NLog.Logger GetLogger()
{
var stackTrace = new StackTrace(false);
StackFrame[] frames = stackTrace.GetFrames();
if (null == frames) throw new ArgumentException("Stack frame array is null.");
StackFrame stackFrame;
switch (frames.Length)
{
case 0:
throw new ArgumentException("Length of stack frames is 0.");
case 1:
case 2:
stackFrame = frames[frames.Length - 1];
break;
default:
stackFrame = stackTrace.GetFrame(2);
break;
}
Type declaringType = stackFrame.GetMethod()
.DeclaringType;
return declaringType == null ? LogManager.GetCurrentClassLogger() : LogManager.GetLogger(declaringType.FullName);
}
But sadly, the Stacktrace with MEF is very long and i cannot clearly identify the correct caller for the Requester of the ILogger.
So, instead of injecting the ILogger Interface via Constructor Injection, i have created a ILogFactory Interface, that can get injected via Constructor Injection and call then the Create Method on the Factory
public interface ILogFactory
{
#region Public Methods and Operators
/// <summary>
/// Creates a logger with the Callsite of the given Type
/// </summary>
/// <example>
/// factory.Create(GetType());
/// </example>
/// <param name="type">The type.</param>
/// <returns></returns>
ILogger Create(Type type);
#endregion
}
And implemented it:
using System;
using System.ComponentModel.Composition;
[Export(typeof(ILogFactory))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class LogFactory : ILogFactory
{
#region Public Methods and Operators
public ILogger Create(Type type)
{
var logger = new Logger().CreateLogger(type);
return logger;
}
#endregion
}
With the ILogger:
public interface ILogger
{
#region Public Properties
bool IsDebugEnabled { get; }
bool IsErrorEnabled { get; }
bool IsFatalEnabled { get; }
bool IsInfoEnabled { get; }
bool IsTraceEnabled { get; }
bool IsWarnEnabled { get; }
#endregion
#region Public Methods and Operators
void Debug(Exception exception);
void Debug(string format, params object[] args);
void Debug(Exception exception, string format, params object[] args);
void Error(Exception exception);
void Error(string format, params object[] args);
void Error(Exception exception, string format, params object[] args);
void Fatal(Exception exception);
void Fatal(string format, params object[] args);
void Fatal(Exception exception, string format, params object[] args);
void Info(Exception exception);
void Info(string format, params object[] args);
void Info(Exception exception, string format, params object[] args);
void Trace(Exception exception);
void Trace(string format, params object[] args);
void Trace(Exception exception, string format, params object[] args);
void Warn(Exception exception);
void Warn(string format, params object[] args);
void Warn(Exception exception, string format, params object[] args);
#endregion
}
and Implementation of:
using System;
using NLog;
using NLog.Config;
/// <summary>
/// The logging service.
/// </summary>
public class Logger : NLog.Logger, ILogger
{
#region Fields
private string _loggerName;
#endregion
#region Public Methods and Operators
/// <summary>
/// The get logging service.
/// </summary>
/// <returns>
/// The <see cref="ILogger" />.
/// </returns>
public ILogger CreateLogger(Type type)
{
if (type == null) throw new ArgumentNullException("type");
_loggerName = type.FullName;
var logger = (ILogger)LogManager.GetLogger(_loggerName, typeof(Logger));
return logger;
}
To use it... just inject the ILogFactory and calle the Create Method in a Mefed Importing Constructor:
[ImportingConstructor]
public MyConstructor(
ILogFactory logFactory)
{
_logger = logFactory.Create(GetType());
}
hope this helps
If you create a new ExportProvider and cast the ImportDefinition being passed in to a ICompositionElement. You can get the type that the logger is being injected into.
Here is the ExportProvider
public class LoggerExportProvider : ExportProvider
{
private readonly ExportDefinition _loggerExportDefinition;
private readonly Func<string, ILogger> _loggerFactory;
/// <summary>
/// Initializes a new instance of the <see cref="LoggerExportProvider"/> class.
/// </summary>
/// <param name="loggerFactory">The logger factory function.</param>
public LoggerExportProvider(Func<string, ILogger> loggerFactory)
{
_loggerFactory = loggerFactory;
_loggerExportDefinition = new ExportDefinition(typeof (ILogger).FullName, new Dictionary<string, object> {{"ExportTypeIdentity", typeof (ILogger).FullName}});
}
protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
{
IList<Export> exports = new List<Export>();
var compositionElement = definition as ICompositionElement;
if (compositionElement == null || compositionElement.Origin == null)
return exports;
var constraint = definition.Constraint.Compile();
if (constraint(_loggerExportDefinition))
exports.Add(new Export(_loggerExportDefinition, () => _loggerFactory(compositionElement.Origin.DisplayName)));
return exports;
}
}
This is setup in such a way that it will work with any logging framework as you need to pass in a function that will return an ILogger (the Ilogger is our own, you'll have to create your own interface or just make it specific to Nlog). The string being passed to the function is the full class name that the type is being injected too. (compositionElement.Origin.DisplayName)
An example of bootstrapping MEF with this would look like this:
public class Example
{
[Import]
public ILogger Logger { get; set;}
public Example()
{
var aggregatecatalogue = new AggregateCatalog();
aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(typeof (ILogger).Assembly));
aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
var container = new CompositionContainer(aggregatecatalogue, new LoggerExportProvider(s => new MockLogger(s)));
container.ComposeParts(this);
}
}
The code above was copied from a unit test, so I'm just add specific assemblies instead of parsing a directory. The MockLogger is an implementation of the ILogger interface that takes the logging class name (or injecting type) as a parameter to it's constructor.
This doesn't require parsing any stack traces and pulls the information that is otherwise sitting there directly out of MEF.
I'm building small web shop with asp.net mvc and Structuremap ioc/di. My Basket class uses session object for persistence, and I want use SM to create my basket object through IBasket interface. My basket implementation need HttpSessionStateBase (session state wrapper from mvc) in constructor, which is available inside Controller/Action. How do I register my IBasket implementation for SM?
This is my basket interface:
public interface IBasketService {
BasketContent GetBasket();
void AddItem(Product productItem);
void RemoveItem(Guid guid);
}
And SM registration:
ForRequestedType(typeof (IBasketService)).TheDefaultIsConcreteType(typeof (StoreBasketService));
But my StoreBasketService implementation has constructor:
public StoreBasketService(HttpSessionStateBase sessionState)
How do I provide HttpSessionStateBase object to SM, which is available only in controller?
This is my first use of SM IOC/DI, and cann't find solution/example in official documentation and web site ;)
If you absolutely have to have your StoreBasketService use the session, I'd be tempted to define an interface and wrapper around HttpSessionState instead of using HttpSessionStateBase so that you can register it with StructureMap as well.The wrapper would get the session state from the current context. Register the wrapper with StructureMap and then have your StoreBasketService take the interface as the argument to the constructor. Structure map should then know how to create an instance of the interface wrapper and inject it into your StoreBasketService class.
Using an interface and wrapper will allow you to mock the wrapper in your unit tests, muc in the same way HttpSessionStateBase allows mocking the actual session.
public interface IHttpSessionStateWrapper
{
HttpSessionState GetSessionState();
}
public class HttpSessionStateWrapper : IHttpSessionStateWrapper
{
public virtual HttpSessionState GetSessionState()
{
return HttpContext.Current.Session;
}
}
ForRquestedType(typeof(IHttpSessionStateWrapper))
.TheDefaultIsConcreteType(typeof(IHttpSessionStateWrapper));
public class StoreBasketService
{
HttpSessionState session;
public StoreBasketService( IHttpSessionstateWrapper wrapper )
{
session = wrapper.GetSessionState();
}
// basket implementation ...
}
However, you can have StructureMap actually store your basket in the session using .CacheBy(InstanceScope.HttpContext) when registering it. It may actually be better to have your StoreBasketService implement internal storage instead of storing things in the session -- then you lose the dependency on the session state entirely (from the perspective of your class) and your solution could be simpler. Your internal storage could be a Dictionary<Guid,Product> since this is how you access them via your interface.
See also:
http://www.lostechies.com/blogs/chad_myers/archive/2008/07/15/structuremap-basic-scenario-usage.aspx
http://www.lostechies.com/blogs/chad_myers/archive/2008/07/17/structuremap-medium-level-usage-scenarios.aspx
ForRequestedType<IBasketService>()
.TheDefault.Is.OfConcreteType<StoreBasketService>()
.WithCtorArg("sessionState").EqualTo(HttpContext.Current.Session);
?? does that work?
I just started with StructureMap, and I do not get the results you are describing.
I performed a simple test using a simple class, configuring Structuremap to cacheby HttpContext, and from what I can see, CacheBy.HttpContext means within the same request you will get the same instance... not within the same Session
The constructor of my class, sets the date/time in a private field
I have a button which gets 2 instances of MyClass with one second interval...
It then display the time of both instances in a label.
Pressing the first time this button, object A and B are same instance, as their creation time is exactly the same, as expected.
Clicking the button a second time, you would expect the creation time to not have changed if instances would be cached in session... however, in my test I get a new creation time ...
Structuremap configuration:
ObjectFactory.Initialize(x=>x.ForRequestedType<MyClass>(). CacheBy(InstanceScope.HttpContext));
Button clicked event of test page
protected void btnTest_Click(object sender, EventArgs e)
{
MyClass c = ObjectFactory.GetInstance<MyClass>();
System.Threading.Thread.Sleep(1000);
MyClass b = ObjectFactory.GetInstance<MyClass>();
lblResult.Text = String.Format("cache by httpcontext First:{0} Second:{1} session id {2} ", c.GetTimeCreated(), b.GetTimeCreated(),Session.SessionID);
}
MyClass
public class MyClass
{
private DateTime _timeCreated;
public MyClass()
{
_timeCreated = DateTime.Now;
}
public string GetTimeCreated()
{
return _timeCreated.ToString("dd/MM/yyyy hh:mm:ss");
}
}
You could also use one of the ObjectFactory.Inject methods to inject the HttpSessionStateBase into StructureMap. It would then invoke the constructor with the injected HttpSessionStateBase.
I just made my first attempt at creating an custom scope... build a small web application with it, and as far as I can see, it seems to work. This will cache the object inside the current user session and will return the same object as long as you remain inside the same session:
public class HttpSessionBuilder : CacheInterceptor
{
private readonly string _prefix = Guid.NewGuid().ToString();
protected override CacheInterceptor clone()
{
return this;
}
private string getKey(string instanceKey, Type pluginType)
{
return string.Format("{0}:{1}:{2}", pluginType.AssemblyQualifiedName, instanceKey, this._prefix);
}
public static bool HasContext()
{
return (HttpContext.Current.Session != null);
}
protected override bool isCached(string instanceKey, Type pluginType)
{
return HttpContext.Current.Session[this.getKey(instanceKey, pluginType)] != null;
}
protected override object retrieveFromCache(string instanceKey, Type pluginType)
{
return HttpContext.Current.Session[this.getKey(instanceKey, pluginType)];
}
protected override void storeInCache(string instanceKey, Type pluginType, object instance)
{
HttpContext.Current.Session.Add(this.getKey(instanceKey, pluginType), instance);
}
}
You have to configure the ObjectFactory as follows in the global.asax Application_start
ObjectFactory.Initialize(x=>
x.ForRequestedType<MyClass>().InterceptConstructionWith(new HttpSessionBuilder()));