ASP.NET Core Web API - HangFire Cron schedule failed to run - c#

In ASP.NET Core-6 application, I am implementing Recurring Job using HangFire.
I have a 3rd party api (JSON) to be consumed and inserted into the database 2am daily.
API:
"https://api.thirdpartycompany.com:2233/api/BranchDetail"
In appsettings.json I have:
"Endpoints": {
"branchUrl": "https://api.thirdpartycompany.com:2233/api/BranchDetail"
}
API:
{
"Branches": [
{
"BranchName": "Accra",
"BranchNumber": 1,
"Country": "Ghana"
},
{
"BranchName": "Kumasi",
"BranchNumber": 2,
"Country": "Ghana"
},
...
}
The core is as shown below:
Entity:
public class Branch
{
public int Id { get; set; }
public string BranchName { get; set; }
public int BranchNumber { get; set; }
}
DTO:
public class BranchCreateUpdateDto
{
public string BranchName { get; set; }
public int BranchNumber { get; set; }
}
public class BranchResponse
{
public List<BranchCreateUpdateDto> Branches
{
get;
set;
}
}
BaseResponse:
public class BaseResponse
{
public bool Success { get; set; } = true;
public string Message { get; set; }
}
Mapping:
public class AdminMapperProfile: Profile
{
public AdminMapperProfile()
{
CreateMap<BranchCreateUpdateDto, Branch>().ReverseMap();
}
}
Repository:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly ApplicationDbContext _dbContext;
private readonly DbSet<T> _dbSet;
public GenericRepository(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
_dbSet = _dbContext.Set<T>();
}
public async Task InsertAsync(T entity)
{
await _dbSet.AddAsync(entity);
}
public T GetById(string id)
{
return _dbSet.Find(id);
}
public void Update(T entity)
{
_dbContext.Set<T>().Update(entity);
}
}
Interface:
public interface IAdminBranchRepository : IGenericRepository<Branch>
{
}
Implementation:
public class AdminBranchRepository : GenericRepository<Branch>, IAdminBranchRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly DbSet<Branch> _branches;
public AdminBranchRepository(ApplicationDbContext dbContext) : base(dbContext)
{
_dbContext = dbContext;
_branches = _dbContext.Set<Branch>();
}
}
IUnitOfWork:
public interface IUnitOfWork : IDisposable
{
IAdminBranchRepository Branches { get; }
Task Save();
}
UnitOfWork:
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _dbContext;
private IAdminBranchRepository _branches;
public UnitOfWork(
ApplicationDbContext dbContext,
)
{
_dbContext = dbContext;
}
public IAdminBranchRepository Branches => _branches ??= new AdminBranchRepository(_dbContext);
public async Task Save()
{
await _dbContext.SaveChangesAsync();
}
}
Service:
Interface:
Task<BaseResponse> CreateBranchAsync();
Implementation:
public class AdminBranchService : IAdminBranchService
{
private readonly ApplicationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
private readonly IConfiguration _config;
private readonly HttpClient _myClient;
public AdminBranchService(
ApplicationDbContext dbContext,
IUnitOfWork unitOfWork,
ILogger logger,
IMapper mapper,
IConfiguration config,
HttpClient myClient
)
{
_dbContext = dbContext;
_mapper = mapper;
_unitOfWork = unitOfWork;
_logger = logger;
_config = config;
_myClient = myClient;
}
public async Task<BaseResponse> CreateBranchAsync()
{
var branchResponse = new BaseResponse();
var branches = new List<Branch>();
try
{
string branchUrl = _config.GetSection("Endpoints").GetValue<string>("branchUrl");
_myClient.DefaultRequestHeaders.Accept.Clear();
_myClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = _myClient.GetAsync(branchUrl).Result;
var stringResult = response.Content.ReadAsStringAsync().Result;
BranchResponse list = JsonConvert.DeserializeObject<BranchResponse>(stringResult);
foreach (var singleBranch in list.Branches)
{
Branch res = new Branch();
if (_dbContext.Branches.Any(x => x.BranchName == singleBranch.BranchName))
{
res.BranchNumber = singleBranch.BranchNumber;
_unitOfWork.Branches.Update(res);
}
else
{
//set all fields here
res.BranchName = singleBranch.BranchName;
res.BranchNumber = singleBranch.BranchNumber;
await _unitOfWork.Branches.InsertAsync(res);
}
await _unitOfWork.Save();
}
_logger.Information("Branches Added Successfully");
}
catch (Exception ex)
{
_logger.Error("An Error occured " + ex.ToString());
}
return branchResponse;
}
}
ConnectionConfiguration:
public static class ConnectionConfiguration
{
public static void AddDbContextAndConfigurations(this IServiceCollection services, IWebHostEnvironment env, IConfiguration config)
{
services.AddDbContextPool<ApplicationDbContext>(options =>
{
string connStr;
connStr = config.GetConnectionString("DefaultConnection");
options.UseSqlServer(connStr);
});
}
}
HangFireServiceExtension:
public static class HangFireServiceExtension
{
public static void AddHangFireConfigurations(this IServiceCollection services, IConfiguration config)
{
string connStr;
connStr = config.GetConnectionString("DefaultConnection");
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(connStr, new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true
}));
services.AddHangfireServer();
}
}
Program.cs:
builder.Services.ConfigureAutoMappers();
// Db Injection
builder.Services.AddDbContextAndConfigurations(environment, configuration);
// HangFire
builder.Services.AddHangFireConfigurations(configuration);
app.UseHangfireDashboard();
RecurringJob.AddOrUpdate<IAdminBranchService>("Post_All_Branches", service => service.CreateBranchAsync(),
Cron.Daily(2, 0), TimeZoneInfo.Local);
What I want to achieve is that by every 2am daily the recurring job should run. It should insert fresh records and update existing ones.
However, on the third day of deployment, no record is found in the database. The HangFire RecurringJob is not running.
Where have I missed it, and how do I correct this?
Thank you

Related

Mediatr - Generic Requests and Request Handlers

I'm trying to use generics to create an abstract query request handler for aggregate roots in a domain-driven design architecture.
I've got the following classes to setup a Mediatr request.
However, I get a compiler error when using ISender.Send because it thinks my response object is just an object instead a QueryResult<T>.
Do you know what I'm doing wrong?
public interface IQuery<T> : IRequest<T>
where T : class
{
Guid CorrelationId { get; }
}
public abstract class BaseResult
{
protected BaseResult(ResultStatus status, Dictionary<string, List<string>>? errors)
{
this.Status = status;
this.Errors = errors ?? new Dictionary<string, List<string>>();
this.IsSuccess = status == ResultStatus.Success;
}
public bool IsSuccess { get; private set; }
public ResultStatus Status { get; private set; }
public Dictionary<string, List<string>> Errors { get; private set; }
...
}
public class QueryResult<T> : BaseResult
where T : class
{
private QueryResult(T? data, ResultStatus status, Dictionary<string, List<string>>? errors)
: base(status, errors)
{
this.Data = data;
}
public T? Data { get; }
...
}
public class AggregateRootQuery<TAggregate, TDto>
: IRequest<QueryResult<IEnumerable<TDto>>>, IQuery<IEnumerable<TDto>>
where TAggregate : class, IAggregateRoot // IAggregate root is a marker interface to only allow queries to start at the aggregate root when using my EF core DB context wrapper
where TDto : class
{
public AggregateRootQuery(Guid correlationId)
{
this.CorrelationId = correlationId;
}
public Guid CorrelationId { get; }
}
public abstract class BaseAggregateRootQueryHandler<TAggregate, TDto> : IRequestHandler<AggregateRootQuery<TAggregate, TDto>, QueryResult<IEnumerable<TDto>>>
where TAggregate : class, IAggregateRoot
where TDto : class
{
protected BaseAggregateRootQueryHandler(IDbContext dbContext, IMapper mapper, ILogger logger)
{
this.DbContext = dbContext;
this.Mapper = mapper;
this.Logger = logger;
}
protected IDbContext DbContext { get; }
protected IMapper Mapper { get; }
protected ILogger Logger { get; }
public async Task<QueryResult<IEnumerable<TDto>>> Handle(AggregateRootQuery<TAggregate, TDto> request, CancellationToken cancellationToken)
{
var entities = await this.ApplyFilter(this.DbContext.AggregateRoot<TAggregate>())
.ToArrayAsync(cancellationToken);
var dtos = this.MapToDataTransferObjects(entities);
this.Logger.Debug("{Count} {EntityType} read from database", entities.Length, nameof(TAggregate));
return QueryResult<IEnumerable<TDto>>.Success(dtos);
}
protected abstract IQueryable<TAggregate> ApplyFilter(IQueryable<TAggregate> source);
protected virtual IEnumerable<TDto> MapToDataTransferObjects(IEnumerable<TAggregate> source)
{
return this.Mapper.Map<IEnumerable<TDto>>(source);
}
}
Usage
// Domain.Order implements IAggregateRoot
public class OrderQueryHandler : BaseAggregateRootQueryHandler<Domain.Order, OrderDto>
{
public OrderQueryHandler(IDbContext dbContext, IMapper mapper, ILogger logger)
:base(dbContext, mapper, logger)
{
}
protected override IQueryable<Domain.Order> ApplyFilter(IQueryable<Domain.Order> source)
{
return source.OrderBy(x => x.Name);
{
}
var result = await this.Mediator.Send(new AggregateRootQuery<Domain.Order, OrdrDto>(Guid.NewGuid()));
// `IsSuccess` and `Data` have compiler errors because it thinks `result` is an object and not a `QueryResult<IEnumerable<OrderDto>>`
if (!result.IsSuccess || result.Data == null)
{
// Error handing
}

ASP.NET Core Web API - How to Consume 3rd party API Based on Condition

I am given a third party API to consume In my ASP.NET Core-6 Web API.
API:
"https://api.thirdpartycompany.com:2233/api/BranchDetail"
In appsettings.json I have:
"Endpoints": {
"branchUrl": "https://api.thirdpartycompany.com:2233/api/BranchDetail"
}
API:
{
"Branches": [
{
"BranchName": "Accra",
"BranchNumber": 1,
"BranchType": "Human Resource Agency",
"Country": "Ghana"
},
{
"BranchName": "Kumasi",
"BranchNumber": 2,
"BranchType": "Production",
"Country": "Ghana"
},
...
}
The core is as shown below:
Entity:
public class Branch
{
public int Id { get; set; }
public string BranchName { get; set; }
public string BranchType { get; set; }
public int BranchNumber { get; set; }
}
DTO:
public class BranchCreateUpdateDto
{
public string BranchName { get; set; }
public string BranchType { get; set; }
public int BranchNumber { get; set; }
}
public class BranchResponse
{
public List<BranchCreateUpdateDto> Branches
{
get;
set;
}
}
BaseResponse:
public class BaseResponse
{
public bool Success { get; set; } = true;
public string Message { get; set; }
}
Service:
Interface:
Task<BaseResponse> CreateBranchAsync();
Implementation:
public class AdminBranchService : IAdminBranchService
{
private readonly ApplicationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
private readonly IConfiguration _config;
private readonly HttpClient _myClient;
public AdminBranchService(
ApplicationDbContext dbContext,
IUnitOfWork unitOfWork,
ILogger logger,
IMapper mapper,
IConfiguration config,
HttpClient myClient
)
{
_dbContext = dbContext;
_mapper = mapper;
_unitOfWork = unitOfWork;
_logger = logger;
_config = config;
_myClient = myClient;
}
public async Task<BaseResponse> CreateBranchAsync()
{
var branchResponse = new BaseResponse();
var branches = new List<Branch>();
try
{
string branchUrl = _config.GetSection("Endpoints").GetValue<string>("branchUrl");
_myClient.DefaultRequestHeaders.Accept.Clear();
_myClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = _myClient.GetAsync(branchUrl).Result;
var stringResult = response.Content.ReadAsStringAsync().Result;
BranchResponse list = JsonConvert.DeserializeObject<BranchResponse>(stringResult);
foreach (var singleBranch in list.Branches)
{
Branch res = new Branch();
if (_dbContext.Branches.Any(x => x.BranchName == singleBranch.BranchName))
{
res.BranchNumber = singleBranch.BranchNumber;
_unitOfWork.Branches.Update(res);
}
else
{
//set all fields here
res.BranchName = singleBranch.BranchName;
res.BranchNumber = singleBranch.BranchNumber;
await _unitOfWork.Branches.InsertAsync(res);
}
await _unitOfWork.Save();
}
_logger.Information("Branches Added Successfully");
}
catch (Exception ex)
{
_logger.Error("An Error occured " + ex.ToString());
}
return branchResponse;
}
}
As stated earlier, I am consuming a third party API, but I don't want to get all the data.
From
BranchResponse list = JsonConvert.DeserializeObject<BranchResponse>(stringResult);
I want to get or deserialize only the data where BranchType Contains "Human Resource" or "Production"
How do I achieve this?
Thank you
You can filter the list of branches using LINQ once they're deserialized, like so:
List<BranchCreateUpdateDto> branches;
var filtered = branches.Where(x => x.BranchType.StartsWith("Human Resource"));
The AdminBranchService might look like this (I also changed the async calls to be properly awaited):
public class AdminBranchService : IAdminBranchService
{
private readonly ApplicationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
private readonly IConfiguration _config;
private readonly HttpClient _myClient;
public AdminBranchService(
ApplicationDbContext dbContext,
IUnitOfWork unitOfWork,
ILogger logger,
IMapper mapper,
IConfiguration config,
HttpClient myClient)
{
_dbContext = dbContext;
_mapper = mapper;
_unitOfWork = unitOfWork;
_logger = logger;
_config = config;
_myClient = myClient;
}
public async Task<BaseResponse> CreateBranchAsync()
{
var branchResponse = new BaseResponse();
var branches = new List<Branch>();
try
{
string branchUrl = _config.GetSection("Endpoints").GetValue<string>("branchUrl");
_myClient.DefaultRequestHeaders.Accept.Clear();
_myClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await _myClient.GetAsync(branchUrl);
var stringResult = await response.Content.ReadAsStringAsync();
BranchResponse list = JsonConvert.DeserializeObject<BranchResponse>(stringResult);
// filter branches based on BranchType
var filteredBranches = list.Branches
.Where(x => x.BranchType.StartsWith("Human Resource")
|| x.BranchType.StartsWith("Production"));
foreach (var singleBranch in filteredBranches)
{
Branch res = new Branch();
if (_dbContext.Branches.Any(x => x.BranchName == singleBranch.BranchName))
{
res.BranchNumber = singleBranch.BranchNumber;
_unitOfWork.Branches.Update(res);
}
else
{
//set all fields here
res.BranchName = singleBranch.BranchName;
res.BranchNumber = singleBranch.BranchNumber;
await _unitOfWork.Branches.InsertAsync(res);
}
await _unitOfWork.Save();
}
_logger.Information("Branches Added Successfully");
}
catch (Exception ex)
{
_logger.Error("An Error occured " + ex.ToString());
}
return branchResponse;
}
}
May be you can add a middleware to check for the response and filter that out.

Global model in controller ASP.NET Core MVC 6

I have Home / Index where show list of Current Tasks, Completed Tasks and form for creating new task.
I created HomeIndexViewModel for pass models (Completed tasks, Current Tasks and TaskCreateViewModel for form) to Index View and there call (#Model.CompletedTasks, #Model.CurrentTasks and #Model.FormCreate)
But in CreatedTaskViewModel I want to get information about validation errors and render them in View. I init in Controller HomeIndexViewModel and get access from Index(Action) and Create(Action).
Approach worked, but I am not sure what it's good idea.
public class HomeIndexViewModel
{
public List<TaskModel> CompletedTasks { get; set; } = new List<TaskModel>();
public List<TaskModel> CurrentTasks { get; set; } = new List<TaskModel>();
public CreateTaskViewModel FormCreate { get; set; } = new CreateTaskViewModel();
}
public class HomeController : Controller
{
private readonly ITaskRepository _taskRepository;
private HomeIndexViewModel homeIndexViewModel;
public HomeController(IConfiguration configuration)
{
_taskRepository = new TaskRepository(configuration.GetConnectionString("AppDB"));
homeIndexViewModel = new HomeIndexViewModel()
{
CompletedTasks = _taskRepository.GetList("completed");
CurrentTasks = _taskRepository.GetList("current");
};
public ActionResult Index()
{
return View(homeIndexViewModel);
}
public ActionResult Create(CreateTaskViewModel task)
{
if (ModelState.IsValid)
{
_taskRepository.Create(task);
}
return View(nameof(Index), homeIndexViewModel);
}
I think you could write a service and inject it to your controller:
public interface ISomeService
{
public HomeIndexViewModel GetHomeIndexViewModel(IConfiguration configuration, ITaskRepository taskRepository)
{
//some codes
HomeIndexViewModel homeIndexView = new HomeIndexViewModel()
{
//some codes
};
return homeIndexView;
}
}
public class SomeService : ISomeService
{
public HomeIndexViewModel GetHomeIndexViewModel(IConfiguration configuration, ITaskRepository taskRepository)
{
//some codes
HomeIndexViewModel homeIndexView = new HomeIndexViewModel()
{
//some codes
};
return homeIndexView;
}
}
In your Startup Class:
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddTransient<ISomeService, SomeService>();
.....
}
In your Controller:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ISomeService _someService;
private readonly ITaskRepository _taskRepository;
private readonly IConfiguration _configuration;
public HomeController(ILogger<HomeController> logger, ISomeService someService, ITaskRepository taskRepository, IConfiguration configuration)
{
_logger = logger;
_someService = someService;
_taskRepository = taskRepository;
_configuration = configuration;
}
public IActionResult Index()
{
var homeindexviewmodel = _someService.GetHomeIndexViewModel(_configuration,_taskRepository);
//you could get the homeindexviewmodel in other controllers with the same method
return View();
}
}

How can i access a localized string in my controller?

I have setup the startup.cs as
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddRazorPages()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
var cultures = new[]
{
new CultureInfo("el"),
new CultureInfo("en")
};
options.DefaultRequestCulture = new RequestCulture("el");
options.SupportedCultures = cultures;
options.SupportedUICultures = cultures;
});
}
and
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
}
Within my page model i use DI as
public class dashboardModel : CustomBasePage
{
private readonly ILogger<dashboardModel> _logger;
private readonly ApplicationDbContext _dbContext;
private readonly IStringLocalizer _localizer;
public dashboardModel(ILogger<dashboardModel> logger, ApplicationDbContext context, IStringLocalizer localizer)
{
_logger = logger;
_dbContext = context;
_localizer = localizer;
}
public void OnGet()
{
string s = _localizer["Dashboard"];
}
}
where the CustomBasePage inherits from PageModel with a group of properties
public class CustomBasePage : PageModel, ICustomBasePage
{
public Layout Layout { get; set; }
//Localization
public string returnUrl { get; set; }
public IRequestCultureFeature requestCulture { get; set; }
public List<SelectListItem> cultureItems { get; set; }
public CustomBasePage()
{
}
}
Upon running my application i get the error
InvalidOperationException: Unable to resolve service for type
'Microsoft.Extensions.Localization.IStringLocalizer' while attempting to activate
'example.com.Pages.Shared.dashboardModel'.
What i do not quite understand here is why the error is about
example.com.Pages.**Shared**.dashboardModel
and not about the
example.com.Pages.dashboardModel
where the file as dashboard.cshtml physically exists.

Best approach to combine repositories logic

Let's suppose I have 2 repositories:
first is responsible for creating job and return jobId
second is responsible for creating log and take jobId as argument
My goal is to:
save Job and Log simultaneously
prevent situation when in case of error only Job would be saved without Log
What is the most recommended way to get desired result?
I prepared 3 cases which came to my mind but if you see better alternative please share it.
option 1 (getting result and save changes in controller)
public class JobRepository : IJobRepository
{
private readonly Context _context;
public JobRepository(Context context)
{
_context = context;
}
public Guid CreateJob()
{
var job = new Job { Id = Guid.NewGuid() };
_context.Jobs.Add(job);
return job.Id;
}
}
// ...
public class LogRepository : ILogRepository
{
private readonly Context _context;
public LogRepository(Context context)
{
_context = context;
}
public void CreateLog(Guid id)
{
var log = new Log { Jobid = id };
_context.Logs.Add(log);
}
}
// ...
public class JobsController : Controller
{
private readonly Context _context;
private readonly IJobRepository _jobRepository;
private readonly ILogRepository _logRepository;
public JobsController(Context context, JobRepository jobRepository, ILogRepository logRepository)
{
_context = context;
_jobRepository = jobRepository;
_logRepository = logRepository
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create()
{
var id = _jobRepository.CreateJob();
_logRepository.CreateLog(id);
_context.SaveChanges();
return RedirectToAction("Index");
}
}
option 2 (inject one repository into another)
public class JobRepository : IJobRepository
{
private readonly Context _context;
private readonly ILogRepository _logRepository;
public JobRepository(Context context, ILogRepository logRepository)
{
_context = context;
}
public void CreateJob()
{
var job = new Job { Id = Guid.NewGuid() };
_context.Jobs.Add(job);
_logRepository.CreateLog(job.Id);
_context.SaveChanges();
}
}
// ...
public class LogRepository : ILogRepository
{
private readonly Context _context;
public LogRepository(Context context)
{
_context = context;
}
public void CreateLog(Guid id)
{
var log = new Log { Jobid = id };
_context.Logs.Add(log);
}
}
// ...
public class JobsController : Controller
{
private readonly IJobRepository _jobRepository;
public JobsController(JobRepository jobRepository)
{
_jobRepository = jobRepository;
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create()
{
_jobRepository.CreateJob();
return RedirectToAction("Index");
}
}
option 3 (do not use context in controller but declare Save method in each repo)
public class JobRepository : IJobRepository
{
private readonly Context _context;
public JobRepository(Context context)
{
_context = context;
}
public Guid CreateJob()
{
var job = new Job { Id = Guid.NewGuid() };
_context.Jobs.Add(job);
return job.Id;
}
public void Save()
{
_context.SaveChanges();
}
}
// ...
public class LogRepository : ILogRepository
{
private readonly Context _context;
public LogRepository(Context context)
{
_context = context;
}
public void CreateLog(Guid id)
{
var log = new Log { Jobid = id };
_context.Logs.Add(log);
}
public void Save()
{
_context.SaveChanges();
}
}
// ...
public class JobsController : Controller
{
private readonly IJobRepository _jobRepository;
private readonly ILogRepository _logRepository;
public JobsController(JobRepository jobRepository, ILogRepository logRepository)
{
_jobRepository = jobRepository;
_logRepository = logRepository
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create()
{
var id = _jobRepository.CreateJob();
_logRepository.CreateLog(id);
return RedirectToAction("Index");
}
}
As the use case suggests that the operations (saving and logging) should happen as a single unit of work.
I would suggest an approach similar to the third one. But instead of directly injecting both the repositories into the controller. We could create a service that would then make use of the repositories.
Here we can create a service as follows :
public class JobService : IJobService
{
private readonly IJobRepository _jobRepo;
private readonly ILogRepository _logRepo;
public JobRepository(IJobRepository jobRepo, ILogRepository logRepo)
{
_jobRepo = jobRepo;
_logRepo = logRepo;
}
public void CreateJob()
{
var id = _jobRepo.CreateJob();
_logRepo.CreateLog(id);
}
}
public class JobsController : Controller
{
private readonly IJobService _jobService;
public JobsController(IJobService jobService)
{
_jobService = jobService;
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create()
{
_jobService.CreateJob();
return RedirectToAction("Index");
}
}
Additional Reading : The Repository-Service pattern

Categories