ASP.NET nHibernate+Quartz.NET+Unity - asp.net-mvc

I am developing a web application to ASP.NET MVC. To interact with the data repository is used, based on nHibernate. Repositories are instantiated through Unity. To repository resolved ISession per request.
Now I need to add to the project crawlers performing periodic scheduled tasks in the background. To implement a crawler selected Quartz.NET. A project can have several different walkers, while interacting with the database through the repository, if matches the execution of their tasks.
The problem is that the crawler must create another session, because it is based on a request solution is not working (no web request). It was implemented a solution in which I register two different implementations of the ISession, but it does not work. The code samples below.
The following questions arise:
This is correct direction of solving the problem?
How to implement it correctly?
Will not this approach two crawler trying to write to the database, to cause mistake?
Code:
public class DatabaseRepositoriesRegistration : IUnityRegistration
{
public void Register(IUnityContainer container)
{
// web
var connectionString = WebConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
SessionManager.ConnectionString = connectionString;
container.RegisterType<ISession>(new HierarchicalLifetimeManager(),
new InjectionFactory(c => SessionManager.CurrentSession)
);
container.RegisterType<IUnitOfWork, NHibernateUnitOfWork>(new HierarchicalLifetimeManager());
container.RegisterType(typeof(IRepository<>), typeof(NHibernateRepository<>), new HierarchicalLifetimeManager());
// externals
container.RegisterType<ISession>("ext", new HierarchicalLifetimeManager(), new InjectionFactory(c =>
{
var config = Fluently.Configure().
Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(connectionString)
.UseReflectionOptimizer()
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<MappingBeacon>())
.BuildConfiguration();
var sessionFactory = config.BuildSessionFactory();
return sessionFactory.OpenSession();
}));
container.RegisterType(typeof(IRepository<>), typeof(NHibernateRepository<>), "ext", new HierarchicalLifetimeManager(), new InjectionConstructor(new ResolvedParameter<ISession>("ext")));
}
}
public class SchedulerTask : MvcStartupTaskBase
{
private readonly IUnityContainer container;
public SchedulerTask(IUnityContainer container)
{
this.container = container;
}
public override void Run()
{
var scheduler = container.Resolve<IScheduler>();
scheduler.JobFactory = new UnityJobFactory(container);
scheduler.Start();
var job = JobBuilder.Create<UploadConvertionTask>().Build();
var trigger = TriggerBuilder.Create()
.WithDailyTimeIntervalSchedule
(s =>
s.WithIntervalInMinutes(1)
.OnEveryDay()
.StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(0, 0))
)
.Build();
scheduler.ScheduleJob(job, trigger);
}
public class UnityJobFactory : IJobFactory
{
private readonly IUnityContainer container;
public UnityJobFactory(IUnityContainer container)
{
this.container = container;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return (IJob)container.Resolve(bundle.JobDetail.JobType, "ext");
}
public void ReturnJob(IJob job)
{
}
}
}
public class UploadConvertionTask: IJob
{
public UploadConvertionTask(IRepository<Upload> uploadRepositiory) {
// for repository session is closed!
}
public void Execute(IJobExecutionContext context)
{
}
}

Your scheduler is set up as a local variable inside the run method. It needs to be a variable that lives around long enough for the scheduler to run the task. Without knowing the details of the whole project, you should set your scheduler to be a singleton that is created when the application starts. Then, reference this singleton instance when you're scheduling jobs.
Ideally, your scheduler should be run as a separate long-running service, such as a windows service.

Related

Add EvenLogging to an IHost container

I’m creating a console app and have recently started adding custom services to the IHost container so I can simply pass the IHost to any number of factory classes and have everything thing I need to configure them. But I’ve gotten stuck when it comes to adding Windows Event Logging as a service, could use some help getting past this.
My Main static method in Program calls CreateHostBuilder and returns an IHostBuilder as shown below.
public static IHostBuilder CreateHostBuilder(string[] args)
{
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("appsettings.json");
IConfiguration configuration = configurationBuilder.Build();
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureLogging((hostContext, logging) =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Information);
logging.AddEventLog(eventViewerSettings =>
{
eventViewerSettings.SourceName = "MeCore2";
eventViewerSettings.LogName = "Application";
eventViewerSettings.MachineName = ".";
});
})
.ConfigureServices(services => services.AddDbContext<MeCore2Context>())
// Add custom service for performing DNS queries
.ConfigureServices(services => services.AddTransient<IDnsQueryService>(DnsQueryFactory.Create))
// Add custom service for Managing Runtime Environment Settings
.ConfigureServices(services => services.AddTransient<IEnvironmentSettings>(EnvironmentSettingsFactory.Create))
// Add custom service for Managing String Extractions
.ConfigureServices(services => services.AddTransient<IExtractStringsService>(ExtraxtStringsFactory.Create))
// Add custom service for IP GeoLocation
.ConfigureServices(services => services.AddTransient<IIpGeolocationService>(IpGeolocationFactory.Create));
return hostBuilder;
}
My factory classes are implemented like this.
public static class DnsQueryFactory
{
public static DnsQueryService Create(IServiceProvider serviceProvider)
{
bool exceptionDisplayOnly = serviceProvider.GetRequiredService<IEnvironmentSettings>().WriteErrorsToEventLogs;
IHost host = serviceProvider.GetRequiredService<IHost>();
return new DnsQueryService(exceptionDisplayOnly, host);
}
}
And my concrete service constructors are implemented like this.
public DnsQueryService(bool exceptionDisplayOnly, IHost host)
{
this.exceptionDisplayOnly = exceptionDisplayOnly;
this.logger = host.Services.GetRequiredService<ILogger>();
this.environmentSettings = host.Services.GetRequiredService<IEnvironmentSettings>();
}
When I ran the app after setting up in this manner, I was unable to pull an ILogger from the host container, I could though, pull an ILoggerFactory then I needed to take some additional steps before I had a fully functional ILogger.
I would like to be able to pull the ILogger from the Host container with it fully configured and ready to use for exception handling, warnings, and basic information logging. But I'm stumped here as I can't seem to get the right syntax for using the ILoggingBuilder or ILoggerFactory into the Host container.
I started down the path of creating a static class EventLoggingServices that would accept an IServiceProvider finish out the configuration steps and return an ILogger, but this too has got me stumped. I'm close but not where I need to be and can't find a blog that covers this approach, either that or I'm going at this the wrong way, to begin with. Appreciate the help and thanks in advance.
I believe I've answered my own question with the following code, it is writing to the event logs. I implemented a factory method to encapsulate the ILogger as follows.
public static class EventLoggingFactory
{
public static ILogger<IEventLogging> Create(IServiceProvider serviceProvider)
{
return new EventLogging().EventLogger;
}
}
public class EventLogging : IEventLogging
{
#region *-- Private Members --*
private ILogger<IEventLogging> _logger = null;
#endregion
public ILogger<IEventLogging> EventLogger { get { return this._logger; } }
public EventLogging()
{
EventLogSettings settings = new EventLogSettings();
settings.LogName = "Application";
settings.SourceName = "MeCore2";
settings.MachineName = ".";
ILoggerFactory loggerFactory = new LoggerFactory();
loggerFactory.AddProvider(new EventLogLoggerProvider(settings));
this._logger = loggerFactory.CreateLogger<IEventLogging>();
}
}
public interface IEventLogging
{
ILogger<IEventLogging> EventLogger { get; }
}
And in my HostBuilder the following:
.ConfigureServices(services => services.AddTransient(EventLoggingFactory.Create))
What I haven't considered and I'm still wrapping my head around are service LifeTimes. Using this approach the Ilogger is Transient, but is that the best way to implement it?
The final code block on this post has been a sufficient solution for my needs. With a little more effort I've been able to expand the features used to capture log data for viewing in Windows Event Viewer.

UWP Template 10 and Service Dendency Injection (MVVM) not WPF

I have spent over two weeks searching google, bing, stack overflow, and msdn docs trying to figure out how to do a proper dependency injection for a mobile app that I am developing. To be clear, I do DI every day in web apps. I do not need a crash course on what, who, and why DI is important. I know it is, and am always embracing it.
What I need to understand is how this works in a mobile app world, and in particular a UWP Template 10 Mobile app.
From my past, in a .net/Asp app I can "RegisterType(new XYZ).Singleton() blah" {please forgive syntax; just an example} in App_Start.ConfigureServices. This works almost identical in .netcore, granted some syntactic changes.
My problem is now I am trying to provide my api is going to an UWP app that needs to digest my IXYZ service. By no means do I think that they should "new" up an instance every time. There has to be a way to inject this into a container on the UWP side; and I feel I am missing something very simple in the process.
Here is the code I have:
App.xaml.cs
public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
// TODO: add your long-running task here
//if (args.Kind == ActivationKind.LockScreen)
//{
//}
RegisterServices();
await NavigationService.NavigateAsync(typeof(Views.SearchCompanyPage));
}
public static IServiceProvider Container { get; private set; }
private static void RegisterServices()
{
var services = new ServiceCollection();
services.AddSingleton<IXYZ, XYZ>();
Container = services.BuildServiceProvider();
}
MainPage.xaml.cs:
public MainPage()
{
InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
}
MainPageViewModel:
public class MainPageViewModel : ViewModelBase
{
private readonly IXYZ _xyz;
public MainPageViewModel(IXYZ xyz)
{
//Stuff
_xyz= xyz;
}
}
I now get the error:
XAML MainPage...ViewModel type cannot be constructed. In order to be constructed in XAML, a type cannot be abstract, interface nested generic or a struct, and must have a public default constructor.
I am willing to use any brand of IoC Container, but what I need is an example of how to properly use DI for services in a UWP app. 99.9% of questions about DI is about Views (i.e. Prism?) not just a simple DI for a service (i.e. DataRepo; aka API/DataService).
Again, I feel I am missing something obvious and need a nudge in the right direction. Can somebody show me an example project, basic code, or a base flogging on how I should not be a programmer...please don't do that (I don't know if my ego could take it).
You can try to Microsoft.Hosting.Extensions just like ASP.NET, there's an implementation on Xamarin.Forms by James Montemagno, as well it can be used in UWP I have tried and it works perfectly. You have to change some parts in order to get it working.
In OnLaunched Method add Startup.Init();
public static class Startup
{
public static IServiceProvider ServiceProvider { get; set; }
public static void Init()
{
StorageFolder LocalFolder = ApplicationData.Current.LocalFolder;
var configFile = ExtractResource("Sales.Client.appsettings.json", LocalFolder.Path);
var host = new HostBuilder()
.ConfigureHostConfiguration(c =>
{
// Tell the host configuration where to file the file (this is required for Xamarin apps)
c.AddCommandLine(new string[] { $"ContentRoot={LocalFolder.Path}" });
//read in the configuration file!
c.AddJsonFile(configFile);
})
.ConfigureServices((c, x) =>
{
// Configure our local services and access the host configuration
ConfigureServices(c, x);
}).
ConfigureLogging(l => l.AddConsole(o =>
{
//setup a console logger and disable colors since they don't have any colors in VS
o.DisableColors = true;
}))
.Build();
//Save our service provider so we can use it later.
ServiceProvider = host.Services;
}
static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services)
{
//ViewModels
services.AddTransient<HomeViewModel>();
services.AddTransient<MainPageViewModel>();
}
static string ExtractResource(string filename, string location)
{
var a = Assembly.GetExecutingAssembly();
using (var resFilestream = a.GetManifestResourceStream(filename))
{
if (resFilestream != null)
{
var full = Path.Combine(location, filename);
using (var stream = File.Create(full))
{
resFilestream.CopyTo(stream);
}
}
}
return Path.Combine(location, filename);
}
}
Injecting a ViewModel is possible as well which is pretty nice.
With help from #mvermef and the SO question Dependency Injection using Template 10 I found a solutions. This turned out to be a rabbit hole where at every turn I ran into an issue.
The first problem was just getting Dependency Injection to work. Once I was able to get that figured out from the sources above I was able to start injecting my services into ViewModels and setting them to the DataContext in the code behind.
Then I ran into an injection issue problem with injecting my IXYZ services into the ViewModels of UserControls.
Pages and their ViewModels worked great but I had issues with the DataContext of the UserControl not being injected with UserControl's ViewModel. They were instead getting injected by the Page's ViewModel that held it.
The final solution turned out to be making sure that the UserControl had the DataContext being set in XAML not the code behind, as we did with the Pages, and then creating a DependencyProperty in the code behind.
To show the basic solution read below.
To make it work I started with:
APP.XAML.CS
public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
// long-running startup tasks go here
RegisterServices();
await Task.CompletedTask;
}
private static void RegisterServices()
{
var services = new ServiceCollection();
services.AddSingleton<IRepository, Repository>();
services.AddSingleton<IBinderService, BinderServices>();
**//ViewModels**
**////User Controls**
services.AddSingleton<AddressesControlViewModel, AddressesControlViewModel>();
services.AddSingleton<CompanyControlViewModel, CompanyControlViewModel>();
**//ViewModels**
**////Pages**
services.AddSingleton<CallListPageViewModel, CallListPageViewModel>();
services.AddSingleton<CallListResultPageViewModel, CallListResultPageViewModel>();
etc....
Container = services.BuildServiceProvider();
}
public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
**//INJECT THE VIEWMODEL FOR EACH PAGE**
**//ONLY THE PAGE NOT USERCONTROL**
if (page is CallListPage)
{
return Container.GetService<CallListPageViewModel>();
}
if (page is CallListResultPage)
{
return Container.GetService<CallListResultPageViewModel>();
}
etc...
return base.ResolveForPage(page, navigationService);
}
In the code behind for the Page
CALLLISTPAGE.XAML.CS
public CallListPage()
{
InitializeComponent();
}
CallListPageViewModel _viewModel;
public CallListPageViewModel ViewModel
{
get { return _viewModel ?? (_viewModel = (CallListPageViewModel)DataContext); }
}
In your XAML add your UserControl
CALLLISTPAGE.XAML
<binder:CompanyControl Company="{x:Bind ViewModel.SelectedCompany, Mode=TwoWay}"/>
In your UserControl make sure to add the DataContext to the XAML NOT the code behind like we did with the pages.
COMPANYCONTROL.XAML
<UserControl.DataContext>
<viewModels:CompanyControlViewModel x:Name="ViewModel" />
</UserControl.DataContext>
In the UserControl Code Behind add a Dependency Property
COMPANYCONTROL.XAML.CS
public static readonly DependencyProperty CompanyProperty = DependencyProperty.Register(
"Company", typeof(Company), typeof(CompanyControl), new PropertyMetadata(default(Company), SetCompany));
public CompanyControl()
{
InitializeComponent();
}
public Company Company
{
get => (Company) GetValue(CompanyProperty);
set => SetValue(CompanyProperty, value);
}
private static void SetCompany(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as CompanyControl;
var viewModel = control?.ViewModel;
if (viewModel != null)
viewModel.Company = (Company) e.NewValue;
}
In the end I am not sure if this is an elegant solution but it works.

MVC - Simple Injector and Attribute calling the Context (EF) Throwing exceptions

If I start my application and let it settle, it works great.
However, when I debug my application and if I close the browser tab before it initializes anything and then call another like localhost:81/Home/Test, it throws an exception on retrieving data from DB (EF).
This exception occurs during a call to a Filter CultureResolver which then calls LanguageService. Inside LanguageService there is a call to the DB to retrieve all the available languages.
I got many different exceptions, like:
The context cannot be used while the model is being created. This
exception may be thrown if the context is used inside the
OnModelCreating method or if the same context instance is accessed by
multiple threads concurrently. Note that instance members of
DbContext and related classes are not guaranteed to be thread safe.
Object reference not set to an instance of an object.
The underlying provider failed on Open.
Those exceptions occur all in the same query, it depends on how much time I left the first tab running.
So it seems it's something like Thread-Unsafe code or this query trying to get items before the Context is initialized.
I've the following:
SimpleInjectorInitializer.cs
public static class SimpleInjectorInitializer
{
/// <summary>Initialize the container and register it as MVC3 Dependency Resolver.</summary>
public static void Initialize()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
InitializeContainer(container);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters, container);
}
private static void InitializeContainer(Container container)
{
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
/* Bindings... */
container.RegisterPerWebRequest<IAjaxMessagesFilter, AjaxMessagesFilter>();
container.RegisterPerWebRequest<ICustomErrorHandlerFilter, CustomErrorHandlerFilter>();
container.RegisterPerWebRequest<ICultureInitializerFilter, CultureInitializerFilter>();
}
}
FilterConfig.cs
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters, Container container)
{
filters.Add(container.GetInstance<ICultureInitializerFilter>());
filters.Add(container.GetInstance<ICustomErrorHandlerFilter>());
filters.Add(container.GetInstance<IAjaxMessagesFilter>());
}
}
CultureResolver.cs
public class CultureResolver : ICultureResolver
{
ILanguageService Service;
public CultureResolver(ILanguageService Service)
{
this.Service = Service;
}
public string Resolve(string CultureCode)
{
// Get the culture by name or code (pt / pt-pt)
ILanguageViewModel language = Service.GetByNameOrCode(CultureCode);
if (language == null)
{
// Get the default language
language = Service.GetDefault();
}
return language.Code;
}
}
LanguageService.cs
public class LanguageService : ILanguageService
{
IMembership membership;
ChatContext context;
ILanguageConverter converter;
public LanguageService(
ChatContext context,
IMembership membership,
ILanguageConverter converter
)
{
this.membership = membership;
this.context = context;
this.converter = converter;
}
public virtual ILanguageViewModel GetByNameOrCode(string Text)
{
string lowerText = Text.ToLower();
string lowerSmallCode = "";
int lowerTextHiphen = lowerText.IndexOf('-');
if (lowerTextHiphen > 0)
lowerSmallCode = lowerText.Substring(0, lowerTextHiphen);
Language item = this.context
.Languages
.FirstOrDefault(x => x.Code.ToLower() == lowerText
|| x.SmallCode.ToLower() == lowerText
|| x.SmallCode == lowerSmallCode);
return converter.Convert(item);
}
public virtual ILanguageViewModel GetDefault()
{
Language item = this.context
.Languages
.FirstOrDefault(x => x.Default);
return converter.Convert(item);
}
}
This is the query that is giving me the exceptions
Language item = this.context
.Languages
.FirstOrDefault(x => x.Code.ToLower() == lowerText
|| x.SmallCode.ToLower() == lowerText
|| x.SmallCode == lowerSmallCode);
Global filters in MVC and Web API are singletons. There is only one instance of such filter during the lifetime of your application. This becomes obvious when you look at the following code:
filters.Add(container.GetInstance<ICultureInitializerFilter>());
Here you resolve the filter once from the container and store it for the lifetime of the container.
You however, have registered this type as Scoped using:
container.RegisterPerWebRequest<ICultureInitializerFilter, CultureInitializerFilter>();
You are effectively saying that there should be one instance per web request, most likely because that class depends on a DbContext, which isn't thread-safe.
To allow your filters to have dependencies, you should either make them humble objects, or wrap them in a humble object that can call them. For instance, you can create the following action filter:
public sealed class GlobalActionFilter<TActionFilter> : IActionFilter
where TActionFilter : class, IActionFilter
{
private readonly Container container;
public GlobalActionFilter(Container container) { this.container = container; }
public void OnActionExecuted(ActionExecutedContext filterContext) {
container.GetInstance<TActionFilter>().OnActionExecuted(filterContext);
}
public void OnActionExecuting(ActionExecutingContext filterContext) {
container.GetInstance<TActionFilter>().OnActionExecuting(filterContext);
}
}
This class allows you to add your global filters as follows:
filters.Add(new GlobalActionFilter<ICultureInitializerFilter>(container));
filters.Add(new GlobalActionFilter<ICustomErrorHandlerFilter>(container));
filters.Add(new GlobalActionFilter<IAjaxMessagesFilter>(container));
The GlovalActionFilter<T> will callback into the container to resolve the supplied type every time it is called. This prevents the dependency from becoming captive which prevents the problems you are having.

Keep an nihbernate session open forever?

I am using quartz and nhibernate and ran into a problem. Normally I have all my nhibernate sessions close on finish of a web request but I have a scheduler that starts on application start and I need to pass in a nhibernate session that I think should never be closed.
I am unsure how to do that.
Ninject
public class NhibernateSessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
var sessionFactory = new NhibernateSessionFactory();
return sessionFactory.GetSessionFactory();
}
}
public class NhibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope();
Bind<ISession>().ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession()).InRequestScope();
}
}
Global.aspx
protected void Application_Start()
{
// Hook our DI stuff when application starts
IKernel kernel = SetupDependencyInjection();
// get the reminder service HERE IS WHERE THE PROBLEMS START
IScheduledRemindersService scheduledRemindersService = kernel.Get<IScheduledRemindersService>();
scheduledRemindersService.StartTaskRemindersSchedule();
RegisterMaps.Register();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
public IKernel SetupDependencyInjection()
{
IKernel kernel = CreateKernel();
// Tell ASP.NET MVC 3 to use our Ninject DI Container
DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel));
return kernel;
}
protected IKernel CreateKernel()
{
var modules = new INinjectModule[]
{
new NhibernateModule(),
new ServiceModule(),
new RepoModule()
};
return new StandardKernel(modules);
}
// service that is causing me the problems. Ninject will bind reminderRepo and give it an nihbernate session.
private readonly IReminderRepo reminderRepo;
private readonly ISchedulerFactory schedulerFactory;
public ScheduledRemindersService(IReminderRepo reminderRepo)
{
this.reminderRepo = reminderRepo;
schedulerFactory = new StdSchedulerFactory();
}
public void StartTaskRemindersSchedule()
{
IScheduler scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
JobDetail jobDetail = new JobDetail("TaskRemindersJob",null,typeof(TaskReminderJob));
jobDetail.JobDataMap["reminderRepo"] = reminderRepo;
DateTime evenMinuteDate = TriggerUtils.GetEvenMinuteDate(DateTime.UtcNow);
SimpleTrigger trigger = new SimpleTrigger("TaskRemindersTrigger", null,
DateTime.UtcNow,
null,
SimpleTrigger.RepeatIndefinitely,
TimeSpan.FromMinutes(1));
scheduler.ScheduleJob(jobDetail, trigger);
}
So I need to pass in the reminderRepo into the job as I am doing above
jobDetail.JobDataMap["reminderRepo"] = reminderRepo;
It's the only way you can pass something into a job. Everytime the schedule gets executed a job is recreated and I am assuming it uses the same reminderRepo that I sent in.
My code in the service layer never gets executed again and of course the application start as well(unless I redeploy the site)
Job
public class TaskReminderJob : IJob
{
public void Execute(JobExecutionContext context)
{
JobDataMap dataMap = context.JobDetail.JobDataMap;
ReminderRepo reminderRepo = dataMap["reminderRepo"] as ReminderRepo;
if (context.ScheduledFireTimeUtc.HasValue && context.NextFireTimeUtc.HasValue && reminderRepo != null)
{
DateTime start = context.ScheduledFireTimeUtc.Value;
DateTime end = context.NextFireTimeUtc.Value;
List<PersonalTaskReminder> personalTaskReminders = reminderRepo.GetPersonalTaskReminders(start, end);
if (personalTaskReminders.Count > 0)
{
reminderRepo.DeletePersonalTaskReminders(personalTaskReminders.Select(x => x.ReminderId).ToList());
}
}
}
Reminder Repo. (When this repo gets instantiated a session should be given that will live till the end of the request)
public class ReminderRepo : IReminderRepo
{
private readonly ISession session;
public ReminderRepo(ISession session)
{
this.session = session;
}
public List<PersonalTaskReminder> GetPersonalTaskReminders(DateTime start, DateTime end)
{
List<PersonalTaskReminder> personalTaskReminders = session.Query<PersonalTaskReminder>().Where(x => x.DateToBeSent <= start && x.DateToBeSent <= end).ToList();
return personalTaskReminders;
}
public void DeletePersonalTaskReminders(List<int> reminderId)
{
const string query = "DELETE FROM PersonalTaskReminder WHERE ReminderId IN (:reminderId)";
session.CreateQuery(query).SetParameterList("reminderId", reminderId).ExecuteUpdate();
}
public void Commit()
{
using (ITransaction transaction = session.BeginTransaction())
{
transaction.Commit();
}
}
}
So I need some way of keeping the session alive for my reminders. All my other sessions for all my other repos should be as I have it now. It's only this one that seems to need to live forever.
Edit
I tried to get a new session each time so I am passing the IsessionFactory around. Probably not 100% best but it was the only way I could figure out how to get some new sessions.
I however do not know if my session are being closed through ninject still since I am manually passing in the session now. I thinking now but cannot verify.
**private readonly ISessionFactory sessionFactory;**
private readonly ISchedulerFactory schedulerFactory;
public ScheduledRemindersService(ISessionFactory sessionFactory)
{
**this.sessionFactory = sessionFactory;**
schedulerFactory = new StdSchedulerFactory();
}
public void StartTaskRemindersSchedule()
{
IScheduler scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
JobDetail jobDetail = new JobDetail("TaskRemindersJob",null,typeof(TaskReminderJob));
**jobDetail.JobDataMap["reminderRepo"] = sessionFactory;**
DateTime evenMinuteDate = TriggerUtils.GetEvenMinuteDate(DateTime.UtcNow);
SimpleTrigger trigger = new SimpleTrigger("TaskRemindersTrigger", null,
DateTime.UtcNow,
null,
SimpleTrigger.RepeatIndefinitely,
TimeSpan.FromMinutes(1));
scheduler.ScheduleJob(jobDetail, trigger);
}
So my global.aspx is the same but since ninject now sees that "ScheduledRemindersService" now takes in a nhibernate session factory it binds one for me that I can use.
I then pass it off to the job.
public void Execute(JobExecutionContext context)
{
JobDataMap dataMap = context.JobDetail.JobDataMap;
ISessionFactory sessionFactory = dataMap["reminderRepo"] as ISessionFactory;
if (sessionFactory != null)
{
ISession openSession = sessionFactory.OpenSession();
ReminderRepo reminderRepo = new ReminderRepo(openSession);
}
}
I then pass it into my ReminderRepo so I am guessing it ignores the auto session binding from ninject but I am not 100% sure thus I am not sure if my sessions are being closed.
Is there a reason the job can't just open up a new session every time it runs? Presumably it's running at periodic intervals and not forever ever.
Keeping stuff open forever is usually a sure-fire way to encounter weird behavior.
Hope this helps.
I think the approach to this is altogether wrong. The application scope of a web app is not the place to schedule events or try to persist "state" as it is still a website and the web server will not always have the app "started". This occurs after server restart and before app request, during app pool recycling and other misc cases like load balancing.
The best place to schedule something is either using a windows service or build a console app and then use windows scheduler to run it periodically. Any polling operations like that will cause major issues if you rely on application state.
I would also have serious concerns about the memory cost of letting a data context persist indefinitely even if you could count on it.
Check out building a console app - you'll find it's pretty easy even if you haven't done it before.
The other bonus is when you're sure of what you're doing, console apps can be switched to windows services with relative ease.
I've developed a similar solution recently.
I chose to use a "windows service" to manage my schedules.
Anyway, I don't understand why you do something like this:
ISessionFactory sessionFactory = dataMap["reminderRepo"] as ISessionFactory;
Since I had the same problems I've decided to (only in this situation) get a SessionFactory from StructureMap (that's what I've used) and open a new session by myself.
This is my job:
public class ReminderScheduleJob : IStatefulJob
{
private readonly ILogger _Logger;
private readonly ISessionFactory _SessionFactory;
public ReminderScheduleJob()
{
this._Logger = ObjectFactory.GetInstance<ILogger>();
this._SessionFactory = ObjectFactory.GetInstance<ISessionFactory>();
}
void IJob.Execute(JobExecutionContext context)
{
using (var session = this._SessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
...
}
}
}
}
UPDATE:
This is my nHibernate registry (see structureMap for more infos) which is called at the start-up of my app (asp.net or windows service):
public class NhibernateRegistry: Registry
{
public NhibernateRegistry()
{
For<ISessionFactory>()
.Singleton()
.Use(new BpReminders.Data.NH.NHibernateSessionFactory(myConnectionString, schemaOperation).SessionFactory);
For<IUnitOfWork>()
.HybridHttpOrThreadLocalScoped()
.Use<BpReminders.Data.NH.UnitOfWork>();
For<ISession>()
.HybridHttpOrThreadLocalScoped()
.Use(o => ((BpReminders.Data.NH.UnitOfWork)o.GetInstance<IUnitOfWork>()).CurrentSession);
}
}
I've used a unit of work but that doesn't make any difference for you.
I've defined another registry (structureMap) for Quartz.net cause I want (and that's what they say) it to be singleton:
public class QuartzRegistry : Registry
{
public QuartzRegistry()
{
var properties = new NameValueCollection();
// I set all the properties here cause I persist my jobs etc on a DB.
For<ISchedulerFactory>()
.Singleton()
.Use(new StdSchedulerFactory(properties));
For<IScheduler>()
.Singleton()
.Use(x => x.GetInstance<ISchedulerFactory>().GetScheduler());
}
}
Now, in the global.asax I would start the scheduler asking StructureMap/ninject to resolve the IScheduler:
var scheduler = StructureMap.ObjectFactory.GetInstance<IScheduler>();
scheduler.Start();
I've seen you create a new scheduler in the ScheduledRemindersService:
public ScheduledRemindersService(IReminderRepo reminderRepo)
{
this.reminderRepo = reminderRepo;
schedulerFactory = new StdSchedulerFactory();
}
Since in my example the scheduler is already created and started as singleton you can ask ninject to have the instance ... and schedule your job.
In your job (TaskReminderJob) now you can ask the ObjectFactory to resolve the ISessionFactory (see the construct of my class ReminderScheduleJob).
You can now open a new session and pass the session to your repository.
When everything is done you can dispose your session.
Hope I've been clear enough.
As I told you I've got 2 layers. My web app is responsible to schedule the jobs and I've got a custom windows service (I haven't used the one available with Quartz.net) which is responsible to fetch the triggered events and do some actions.

architectural question asp.net mvc, nhibernate, castle

I have implemented a service which uses a DAOFactory and a NHibernate Helper for the sessions and transactions. The following code is very much simplified:
public interface IService
{
IList<Disease> getDiseases();
}
public class Service : IService
{
private INHibernateHelper NHibernateHelper;
private IDAOFactory DAOFactory;
public Service(INHibernateHelper NHibernateHelper, IDAOFactory DAOFactory)
{
this.NHibernateHelper = NHibernateHelper;
this.DAOFactory = DAOFactory;
}
public IList<Disease> getDiseases()
{
return DAOFactory.getDiseaseDAO().FindAll();
}
}
public class NHibernateHelper : INHibernateHelper
{
private static ISessionFactory sessionFactory;
/// <summary>
/// SessionFactory is static because it is expensive to create and is therefore at application scope.
/// The property exists to provide 'instantiate on first use' behaviour.
/// </summary>
private static ISessionFactory SessionFactory
{
get
{
if (sessionFactory == null)
{
try
{
sessionFactory = new Configuration().Configure().AddAssembly("Bla").BuildSessionFactory();
}
catch (Exception e)
{
throw new Exception("NHibernate initialization failed.", e);
}
}
return sessionFactory;
}
}
public static ISession GetCurrentSession()
{
if (!CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
return SessionFactory.GetCurrentSession();
}
public static void DisposeSession()
{
var session = GetCurrentSession();
session.Close();
session.Dispose();
}
public static void BeginTransaction()
{
GetCurrentSession().BeginTransaction();
}
public static void CommitTransaction()
{
var session = GetCurrentSession();
if (session.Transaction.IsActive)
session.Transaction.Commit();
}
public static void RollbackTransaction()
{
var session = GetCurrentSession();
if (session.Transaction.IsActive)
session.Transaction.Rollback();
}
}
At the end of the day I just want to expose the IService to ASP.NET MVC/Console application/Winform. I can already use the Service in a console application but would like to improve it first. I guess the first improvement would be to inject the interfaces INHibernateHelper and IDAOFactory via castle. But I think the problem is that the NHibernateHelper might cause problems in a asp.net context where NHibernateHelper should run according to the 'Nhibernate session per request' pattern. One question I have is whether this pattern is determined by the nhibernate config section (setting current_session_context_class = web) or can i control this via castle somehow?
I hope this makes sense. The final aim is just to expose THE IService.
Thanks.
Christian
You have two choices..
1) Host it in WCF. This allows you access from any source you want.
2) Abstract away everything that's specific to how the code is being used. In our system for instance we use our own Unit Of Work implementation which is stored differently based on where the code is running. A small example would be storing something using the WCF call context vs. the current thread.

Resources