Global model in controller ASP.NET Core MVC 6 - c#

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();
}
}

Related

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

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

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

Using DBContext in repository class

I want to use my DBcontext in my repository class, but I don't know how at the moment. I've tried looking at other questions, but they don't provide the answer needed specific for my case.
I'll start with my controller:
public class MovieController : ControllerBase
{
IMovieService _movieService;
public MovieController(IMovieService movieService)
{
_movieService = movieService;
}
Service class:
public interface IMovieService
{
Movie RetrieveMovie(long id);
IEnumerable<Movie> RetrieveMovies();
}
public class MovieService : IMovieService
{
IMovieRepository _movieRepository;
public MovieService(IMovieRepository movieRepository)
{
_movieRepository = movieRepository;
}
Repository class:
public interface IMovieRepository
{
Movie GetMovieById(long id);
IEnumerable<Movie> GetMovies();
}
public class MovieRepository : IMovieRepository
{
private readonly MusicContext _db;
public Movie GetMovieById(long id)
{
return _db.Movie.Find(id);
}
DBContext:
public class MovieContext : DbContext
{
public MovieContext(DbContextOptions<MovieContext> options)
: base(options)
{
}
public DbSet<Movie> Movies { get; set; }
}
And last my startup function:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MovieContext")));
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Movie.Api", Version = "v1" });
});
}
At the moment Visual studio warns me # MusicContext _db that this field is never assigned to, which I get. How can I get this to work?
you have to add a constructor to a MovieRepository and use DI
public class MovieRepository : IMovieRepository
{
private readonly MovieContext _db;
public MovieRepository (MovieContext db)
{
_db=db;
}
public Movie GetMovieById(long id)
{
return _db.Movie.Find(id);
}
.....
}

How to pass service config to the Xunit project Test controller?

I have below setup currently.
Startup Class : Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AzureStorageConfig>(Configuration.GetSection("AzureStorageConfig"));
services.AddTransient<IAzureService, AzureService>();
}
//other config settings ...
}
Class: AzureStorageConfig
//store the azure account details etc...
public class AzureStorageConfig
{
public string AzureURL { get; set; }
public string StorageConnectionString { get; set; }
public string AccountName { get; set; }
}
Interface class: IAzureService
public interface IAzureService
{
Task<string> UploadFileAsync(AzureStorageConfig _storageConfig, string filename);
Task<string> DeleteFile(AzureStorageConfig _storageConfig, string filename);
}
Azure class : AzureService that use by interface above
public class AzureService : IAzureService, IDisposable
{
// Here implemented above service methods..
public Task<string> UploadFileAsync(AzureStorageConfig _storageConfig, string filename)
{
//...
}
Task<string> DeleteFile(AzureStorageConfig _storageConfig, string filename)
{
//...
}
}
Image controller: ImageController.cs
[Produces("application/json")]
[Route("api/Images")]
public class ImagesController : Controller
{
#region Private properties
private readonly ApiDbContext _context;
private readonly IMapper _mapper;
private AzureStorageConfig _storageConfig;
public readonly IAzureService _azureService;
#endregion
#region Constructor
public ImagesController(ApiDbContext context, IMapper mapper, IOptions<AzureStorageConfig> storageConfig, IAzureService azureService)
{
_context = context;
_mapper = mapper;
_storageConfig = storageConfig.Value;
_azureService = azureService;
}
// other POST and Delete methods written
public async Task<IActionResult> PostImage(Guid Id, [FromForm] ICollection<IFormFile> files)
{
// here used _storageConfig objects to use account key and names...
}
}
Main issue in below class ( TEST Library ) with xunit
Class: ImageControllerTest
[Collection("TestDb")]
public class ImageControllerTest : IClassFixture<InitializeAutoMap>
{
private ImagesController _controller;
private DatabaseFixture _fixture;
private InitializeAutoMap _initialize;
public ImageControllerTest(DatabaseFixture fixture, InitializeAutoMap initialize)
{
this._fixture = fixture;
this._initialize = initialize;
// How to pass service object and StorageConfig to the main
//ImageController from this TestController for testing.
_controller = new ImagesController(_context,
_initialize.InstanceMapper,
/*<here I want to pass storageConfig object>*/,
/*<here I want to pass service object>*/
);
}
// other [Fact] testing done.
// other codes..
}
How to it will possible to get injected to Xunit ImageControllerTest constructor.
storageConfig class object and
IAzureService object
into the ImageController from Xunit ImageControllerTest method.?
Share me if you have idea or solutions.

Categories