Why doesn't the IOptions pattern work in my Azure function? - c#

I don't understand why the IOptions pattern doesn't work in an Azure function type of project. The values are just not loaded in the variables.
This is my Startup.cs code:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddOptions<ExpOptions>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("ExpOptions").Bind(settings);
});
}
}
Here is my function class:
public class Sum2
{
private readonly IConfiguration _configuration;
private readonly ExpOptions _settings;
public Sum2(IConfiguration configuration, IOptions<ExpOptions> options)
{
_configuration = configuration;
_settings = options.Value;
}
[FunctionName("Sum2")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
// ExpOptions options = _configuration.GetSection("ExpOptions").Get<ExpOptions>(); // this line would work but it's not loading the settings through IOptions pattern
var message = $"{JsonConvert.SerializeObject(_settings)}";
log.LogInformation(message);
return new OkObjectResult(message);
}
}
Here is the options class:
public class ExpOptions
{
public string ConnStr { get; set; }
public string BaseAddress { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And finally in the Azure function, in the Configurations tab, I have the values added like so:
ExpOptions:ConnString
ExpOptions:BaseAddress
ExpOptions:UserName
ExpOptions:Password

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

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.

Use Interface to resolve MediatR IRequestHandler instead of the class

I have implement MediatR in .net 5 application and want to resolve require dependencies using Handler interfaces. Currently I using class name to resolve as following
_mediator.Send(new GetDeviceByIMEI(imei)); // want to use interface ??
//var result = await _mediator.Send(IGetHandHeldByIMEI????);
full code reference as following;
Handler Interface
public interface IGetDeviceByIMEIEventHandler
{
Task<DeviceWrapperDataView> Handle(GetDeviceByIMEI request, CancellationToken cancellationToken);
}
Query Interface
public interface IGetDeviceByIMEI
{
string IMEI { get; set; }
}
Query
public class GetDeviceByIMEI: IRequest<DeviceWrapperDataView>
{
public string IMEI { get; set; }
public GetDeviceByIMEI(string imei)
{
this.IMEI = imei;
}
}
Handler
public class GetDeviceByIMEIEventHandler : IRequestHandler<GetDeviceByIMEI, DeviceWrapperDataView>, IGetDeviceByIMEIEventHandler
{
private readonly IDeviceEntity _DeviceEntity;
public GetDeviceByIMEIEventHandler(IDeviceEntity DeviceEntity)
{
_DeviceEntity = DeviceEntity;
}
public async Task<DeviceWrapperDataView> Handle(GetDeviceByIMEI request, CancellationToken cancellationToken)
{
// code to get data
return DeviceOutput;
}
}
API controller
private readonly IMediator _mediator;
public DeviceController(
IMediator mediator)
{
_mediator = mediator;
}
[HttpGet()]
public async Task<IActionResult> GetDeviceByIMEI(string imei)
{
Var result = await _mediator.Send(new GetDeviceByIMEI(imei));
// want to use
}
To do that you have to register your handler in the container with each of the class that inherit your query interface.
For instance with the code you provided.
public interface IGetDeviceByIMEI : IRequest<DeviceWrapperDataView>
{
string IMEI { get; set; }
}
public class GetDeviceByIMEI: IGetDeviceByIMEI
{
public string IMEI { get; set; }
public GetDeviceByIMEI(string imei)
{
this.IMEI = imei;
}
}
public class AnotherGetDeviceByIMEI: IGetDeviceByIMEI
{
public string IMEI { get; set; }
public GetDeviceByIMEI(string imei)
{
this.IMEI = imei;
}
}
public class GetDeviceByIMEIEventHandler : IRequestHandler<IGetDeviceByIMEI, DeviceWrapperDataView>, IGetDeviceByIMEIEventHandler
{
private readonly IDeviceEntity _DeviceEntity;
public GetDeviceByIMEIEventHandler(IDeviceEntity DeviceEntity)
{
_DeviceEntity = DeviceEntity;
}
public async Task<DeviceWrapperDataView> Handle(IGetDeviceByIMEI request, CancellationToken cancellationToken)
{
// code to get data
return DeviceOutput;
}
}
Once you have done that, you have to register the handler in your container with each use case.
For instance in .Net Core, you can do it with the serviceCollection in the StartUp class.
serviceCollection.AddTransient<IRequestHandler<GetDeviceByIMEI, DeviceWrapperDataView>, GetDeviceByIMEIEventHandler >();
serviceCollection.AddTransient<IRequestHandler<AnotherGetDeviceByIMEI, DeviceWrapperDataView>, GetDeviceByIMEIEventHandler >();
Regards.

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