Учитывая, что регистратор может быть вызван в запросе, а также может быть вызван вне запроса.Если задание запущено и нет запроса, HttpContext
будет null
Рассмотрите возможность отделения регистратора от проблем реализации, таких как HttpContext
, и абстрагируйте процесс отображения пути и принятияПреимущество внедрения зависимостей
public interface IPathProvider {
string MapPath(string path);
}
Следует также избегать запаха кода статического регистратора, так как это затрудняет поддержку и тестирование кода в отдельности.
public interface ILogger {
void log(string message);
//...
}
public class Logger : ILogger {
private readonly IPathProvider pathProvider;
public Logger(IPathProvider pathProvider) {
this.pathProvider = pathProvider;
}
public void log(string strLog) {
FileStream fileStream = null;
string username = Environment.UserName;
string logFilePath = pathProvider.MapPath("~/log/Log.txt");
var logFileInfo = new FileInfo(logFilePath);
var logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName);
double fileSize = ConvertBytesToMegabytes(logFileInfo.Length);
if (fileSize > 30) {
string FileDate = DateTime.Now.ToString().Replace("/", "-").Replace(":", "-");
string oldfilepath = pathProvider.MapPath("~/log/log-" + FileDate + ".txt");
File.Move(logFileInfo.FullName, oldfilepath);
}
if (!logFileInfo.Exists) {
fileStream = logFileInfo.Create();
} else {
fileStream = new FileStream(logFilePath, FileMode.Append);
}
using(fileStream) {
using (var log = new StreamWriter(fileStream)) {
log.WriteLine(DateTime.Now.ToString("MM-dd HH:mm:ss") + " " + username + " " + strLog);
log.Close();
}
}
}
}
и выполнение задания явно зависитна абстракции.
public class RetryTempJob : IJob {
private readonly ILogger logger;
public RetryTempJob(ILogger logger) {
this.logger = logger;
}
public async Task Execute(IJobExecutionContext context) {
try {
logger.log("Executing Job");
new ProcessOrder().retryFailedOrders();
//logger.log("Done Executing Syspro Job");
await Console.Error.WriteLineAsync("Done Executing Syspro Job");
} catch (Exception se) {
await Console.Error.WriteLineAsync("" + se.InnerException);
}
}
}
Здесь можно абстрагироваться от большего, но это немного выходит за рамки примера.
Теперь, когда проблемы проектирования были решены,мы можем взглянуть на реализацию поставщика пути, чтобы позаботиться о ситуации HttpContext
.
Server.MapPath()
требует HttpContext
, а HostingEnvironment.MapPath
- нет.
Ссылка В чем разница между Server.MapPath и HostingEnvironment.MapPath?
Реализация может попытаться проверить контекст на ноль, но Server.MapPath()
в конце концов вызовет HostingEnvironment.MapPath()
, поэтому было бы лучше просто использовать HostingEnvironment.MapPath()
public class PathProvider : IPathProvider {
public string MapPath(string path) {
return HostingEnvironment.MapPath(path);
}
}
То, что осталосьсейчас нужно настроить планировщик так, чтобы он позволял внедрение зависимостей, а вам решать, какую инфраструктуру DI вы хотите использовать.
Создайте фабрику заданий, которая наследуется от фабрики заданий Quartz.NET по умолчанию SimpleJobFactory
.Эта новая фабрика заданий будет принимать IServiceProvider
в своем конструкторе и переопределять метод NewJob()
по умолчанию, предоставляемый SimpleJobFactory
.
class MyDefaultJobFactory : SimpleJobFactory {
private readonly IServiceProvider container;
public MyDefaultJobFactory(IServiceProvider container) {
this.container = container;
}
public override IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) {
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
try {
// this will inject any dependencies that the job requires
return (IJob) this.container.GetService(jobType);
} catch (Exception e) {
var errorMessage = string.Format("Problem instantiating job '{0}'.", jobType.FullName);
throw new SchedulerException(errorMessage, e);
}
}
}
Есть много платформ DI на выбор, но дляВ этом примере я использую .Net Core Dependency Injection Extension, который из-за модульной природы .Net Core позволяет легко добавить его в ваш проект.
Наконец, настройте набор служб в приложении.start
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
var services = new ServiceCollection();
var serviceProvider = ConfigureService(services);
var logger = serviceProvider.GetService<ILogger>();
logger.log("About to Setup Retry Job");
var jobScheduler = serviceProvider.GetRequiredService<IScheduler>();
//...add jobs as needed
jobScheduler.ScheduleJob(.....);
//and start scheduler
jobScheduler.Start();
}
private IServiceProvider ConfigureService(IServiceCollection services) {
//Add job dependencies
services.AddSingleton<ILogger, Logger>();
services.AddSingleton<IPathProvider, PathProvider>();
//add scheduler
services.AddSingleton<IScheduler>(sp => {
var scheduler = new StdSchedulerFactory().GetScheduler();
scheduler.JobFactory = new MyDefaultJobFactory(sp);
return scheduler;
});
//...add any other dependencies
//build and return service provider
return services.BuildServiceProvider();
}
Как я уже говорил, вы можете использовать любой другой контейнер DI / IoC, какой захотите.Реорганизованный код теперь достаточно гибок, чтобы вы могли поменять один его на другой, просто заключив его в производный класс IServiceProvider
и передав его фабрике заданий.
И очистив код от проблем, которые делаютЗапах кода, вы можете управлять им проще.