Implementing an Interface on a class with Dependency Injection - c#

I'm developing a Blazor app and somewhere I'm implementing an interface:
public class UserData : IUserData
{
private readonly ISqlDataAccess _db;
public UserData(ISqlDataAccess db)
{
_db = db;
}
public void SomeFunction ()
{
...
}
}
public interface IUserData
{
void SomeFunction();
}
While on .razor I can do: #inject IUserData UserData; and ((UserData)UserData).SomeFunction();; I'm failing to discover how to do it on .cs file.
//There is no such constructor and If I create a new one, then I won't get the _db dependecy injection
IUserData userDate = new UserData();
userDate.SomeFunction();
Edit
So now, when I'm calling the method from the .cs file, the app freezes; it doesn't throw an error and I am able to refresh the page, so it seems it's stuck on the call to the db; but if I call it from the .razor it works flawlessy.
.cs
public AccountService(IUserData userData)
{
_userData = userData;
}
...
public async Task<bool> Validate(string userId, string password)
{
...
try
{
List<UserModel> users = new List<UserModel<();
users = await _userData.GetUsers();
//NEVER GETS HERE
return true;
}
catch (Exception ex)
{
return false;
}
...
}
.razor
#inject IUserData _db;
#code {
private List<UserModel> users;
...
protected override async Task OnInitializedAsync()
{
users = await _db.GetUsers();
}
...
UserData
public class UserData : IUserData
{
private readonly ISqlDataAccess _db;
public UserData(ISqlDataAccess db)
{
_db = db;
}
public Task<List<UserModel>> GetUsers()
{
string sql = "Select *from dbo.Users";
return _db.LoadData<UserModel, dynamic>(sql, new { });
}
...
}
IUserData
public interface IUserData
{
Task<List<UserModel>> GetUsers();
...
}
Edit2
It turns out I was missing an await when calling Validate() service, and thus not running it asynchronous.

At some point in the program, you need to setup dependency injection. This is most common to do in the ConfigureServices method in Startup.cs by convention.
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonConfigurationProvider("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient <ISqlDataAccess, SqlDataAccess>(); //Second argument is the implementation of the interface
services.AddTransient <IUserData, UserData>();
}
}
You need to pass the ISqlDataAccess to the constructor of the UserData but you had it covered already.
public class UserData : IUserData
{
private readonly ISqlDataAccess _db;
public UserData(ISqlDataAccess db)
{
_db = db;
}
//...
}
Then you need to pass your IUserData to your objects via constructors:
public class ClassWithIUserDataDependency {
private IUserData _userData;
public ClassWithIUserDataDependency (IUserData userData) {
_userData = userData;
}
//rest of the class
}
One note: You would need to pass IUserData to all dependency classes. Based on the name, this looks like a POCO object (If it is not, don't mind this comment) If this is a POCO class, or anything representing a DTO or Data, then it is better to separate db from it and allow users to just new it. If it is not, you may want to change its name.

In Startup.cs you can register your interface and implementation;
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IUserData, Userdata>();
}
Then you can use the interface in a class:
public class TestClass{
private IUserData _userData;
public TestClass(IUserData userdata){
_userData = userdata;
}
}

Related

Implementing unit of work in Entity Framework Core

I have implemented unit of work in the next way in Entity Framework Core.
Context:
public class DaleContext : DbContext, IDaleContext
{
private readonly IConnectionStringProvider _connectionStringProvider;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionStringProvider.ConnectionString);
base.OnConfiguring(optionsBuilder);
}
public DaleContext(IConnectionStringProvider connectionStringProvider)
{
_connectionStringProvider = connectionStringProvider;
}
public DbSet<ProductProducts { get; set; }
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
var modifiedEntries = ChangeTracker.Entries()
.Where(x =x.State == EntityState.Added || x.State == EntityState.Modified).ToList();
return base.SaveChanges();
}
}
Unit of work:
public class UnitOfWork : IUnitOfWork
{
public UnitOfWork(DbContext dbContext)
{
DbContext = dbContext;
}
public DbContext DbContext { get; set; }
public int Commit()
{
return DbContext.SaveChanges();
}
public async Task<intCommitAsync()
{
return await DbContext.SaveChangesAsync();
}
}
Repository:
public class Repository<TEntity: IDisposable, IRepository<TEntity>
where TEntity : class
{
private readonly UnitOfWork _unitOfWork;
public Repository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork as UnitOfWork;
}
public void Dispose()
{
_unitOfWork.DbContext.Dispose();
}
public void Create(TEntity entity)
{
_unitOfWork.DbContext.Entry(entity).State = EntityState.Added;
}
}
I have injected all with autofac:
public static class Container
{
public static ContainerBuilder RegisterInfraestructure(this ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
return containerBuilder;
}
}
public static class Container
{
public static ContainerBuilder RegisterDataResources(this ContainerBuilder containerBuilder)
{
var configurationBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
var configuration = configurationBuilder.Build();
containerBuilder.Register(x =new ConnectionStringProvider(configuration.GetConnectionString("Fgcm.Dale"))
).As<IConnectionStringProvider>();
containerBuilder.RegisterType<DaleContext>().As<DbContext>().As<IDaleContext>();
return containerBuilder;
}
}
public static class Container
{
public static ContainerBuilder RegisterRepository(this ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<CustomerRepository>().As<ICustomerRepository>();
containerBuilder.RegisterType<ProductRepository>().As<IProductRepository>();
containerBuilder.RegisterType<SaleDetailRepository>().As<ISaleDetailRepository>();
containerBuilder.RegisterType<SaleRepository>().As<ISaleRepository>();
containerBuilder.RegisterDataResources();
containerBuilder.RegisterInfraestructure();
return containerBuilder;
}
}
public static class Container
{
public static ContainerBuilder RegisterApplicationServiceResources(this ContainerBuilder
containerBuilder)
{
containerBuilder.RegisterRepository();
containerBuilder.RegisterType<DaleApplicationService>().As<IDaleApplicationService>();
return containerBuilder;
}
}
When I try to save data it doesn't works (doesn't insert data) ... I would like to know why ? Here are when I try to save:
public Product Create(Product product)
{
try
{
_productRepository.Create(product);
_unitOfWork.Commit();
return product;
}
catch (Exception e)
{
Debug.WriteLine(e);
return null;
}
}
And of course all are injected:
private readonly ICustomerRepository _customerRepository;
private readonly IProductRepository _productRepository;
private readonly ISaleDetailRepository _saleDetailRepository;
private readonly ISaleRepository _saleRepository;
private readonly IUnitOfWork _unitOfWork;
public DaleApplicationService(IProductRepository productRepository, ICustomerRepository customerRepository,
ISaleRepository saleRepository, ISaleDetailRepository saleDetailRepository, IUnitOfWork unitOfWork)
{
_productRepository = productRepository;
_customerRepository = customerRepository;
_saleRepository = saleRepository;
_saleDetailRepository = saleDetailRepository;
_unitOfWork = unitOfWork;
}
What am I missing?
PS: all this works with .NET Core Web Api.
As #zolty13 sort of hinted the Instance scope of your DbContext (DaleContext) is probably incorrect. By default Autofac sets the instance scope to Instance per dependency (also known as a "transient" lifetime) which means a new instance of DaleContext is created for every class that depend on it. So your UnitOfWork receives a different instance of DaleContext than IProductRepository. So changes in IProductRepository are not reflected in UnitOfWork.
One way to solve this is to avoid this convoluted wrapping of your DbContext like #Igor suggest. Do you really need this UnitOfWork? Instead, use a repository class that has one instance of DaleContext and make all the DB changes in there and save them.
Alternatively (if you really think you need a UnitOfWork) you can register your DaleContext with an instance per request scope. Do note: Entity Framework's DbContext is not thread safe, so if you need to do concurrent work, this is not a safe approach.
Otherwise, read up on Instance scope.

WPF EF Core depencency injection with generic pattern

currently I'm experimenting with WPF/EFCore and DI. I already set up the Application class to register the different/upcoming services like this:
public partial class App : Application
{
private IHost AppHost;
public App()
{
AppHost = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
ConfigureServices(context.Configuration, services);
})
.Build();
}
private void ConfigureServices(IConfiguration Configuration, IServiceCollection Services)
{
Services.AddDbContext<AppDataContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ProdToolDb"));
});
Services.AddSingleton<MainWindow>();
}
protected override async void OnStartup(StartupEventArgs e)
{
await AppHost.StartAsync();
MainWindow MainWindow = AppHost.Services.GetRequiredService<MainWindow>();
MainWindow.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
using (AppHost)
{
await AppHost.StopAsync();
}
base.OnExit(e);
}
}
Of course, I have the appr. DataContext classes too:
public class AppDataContext : DbContext
{
public AppDataContext(DbContextOptions<AppDataContext> options) : base(options) { }
public DbSet<Capacity> Capacities { get; set; }
public DbSet<CapacityType> CapacityTypes { get; set; }
}
public class AppDataContextFactory : IDesignTimeDbContextFactory<AppDataContext>
{
public AppDataContext CreateDbContext(string[] args = null)
{
DbContextOptionsBuilder<AppDataContext> OptionsBuilder = new DbContextOptionsBuilder<AppDataContext>();
IConfigurationRoot Config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
OptionsBuilder.UseSqlServer(Config.GetConnectionString("ProdToolDb"));
return new AppDataContext(OptionsBuilder.Options);
}
}
I was told in an other question, that in order to get the migration working with EF Core, I have to use IDesignTimeDbContextFactory.
Now, going further, I set up the generic pattern implementation. I created an interface:
public interface IDataService<T>
{
Task<IEnumerable<T>> GetAll();
Task<T> Get(int Id);
Task<T> Create(T Entity);
Task<T> Update(int Id, T Entity);
Task<bool> Delete(int Id);
}
and it's implementation:
public class GenericDataService<T> : IDataService<T> where T : class
{
private readonly AppDataContextFactory ContextFactory;
public GenericDataService(AppDataContextFactory contextFactory)
{
ContextFactory = contextFactory;
}
public async Task<T> Create(T Entity)
{
using (AppDataContext Context = ContextFactory.CreateDbContext())
{
await Context.Set<T>().AddAsync(Entity);
await Context.SaveChangesAsync();
return Entity;
}
}
... other methods omitted for brevity
}
I have the following questions:
Since I use IDesignTimeDbContextFactory in order to have created and configured the DataContext, is this call in the startup redundant?
Services.AddDbContext<AppDataContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ProdToolDb"));
});
Or should I instead of this create a IDesignTimeDbContextFactory service and register it? If yes, then how?
As you see in the IDataService implementation, I use AppDataContextFactory and AppDataContext directly, not with the DI technique. How should I modify the code in order to use DI?
And if I would like to handle everything above with DI, I have to register/add GenericDataService<T> in the startup class, right? How can I do that?
Please advise.
Thank you.
EDIT:
I read the linked blog entries and others too and now I see, why you say, that the generic pattern is an antipattern. I will throw it away and concentrate on the general WPF binding methods and DI of views/viewmodels.

How save globally catched exceptions in database

I am building a web application where I will have a lot of controllers with their corresponding action methods in them.
I want to save every exception in database and for this reason I have created
ExceptionService (DbContext is injected in it).
let's say that this is the general form of my controllers:
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly UserManager userManager;
private readonly IExceptionService exceptionService;
public UserController(UserManager userManager, IExceptionService exceptionService)
{
this.userManager = userManager;
this.exceptionService = exceptionService;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] User user)
{
try
{
//some code
}
catch (Exception e)
{
exceptionService.Save(e);
//some code
}
}
}
In order to avoid so many try-catch blocks I decided to create a filter which looks like this:
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IExceptionService exceptionService;
public ApiExceptionFilterAttribute(IExceptionService exceptionService)
{
this.exceptionService = exceptionService;
}
public override void OnException(ExceptionContext context)
{
Exception e = context.Exception;
exceptionService.Save(e);
//some code
}
}
Code in ConfigureServices method in StartUp.cs looks like this (some code removed for simplicity):
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services
.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default")));
services.AddScoped<UserManager>();
services.AddScoped<SignInManager>();
services.AddScoped<IExceptionService, ExceptionService>();
services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();
ConfgureMvcOptions class looks like this:
public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
private readonly IExceptionService exceptionService;
public ConfigureMvcOptions(IExceptionService exceptionService)
{
this.exceptionService = exceptionService;
}
public void Configure(MvcOptions options)
{
options.Filters.Add(new ApiExceptionFilterAttribute(exceptionService));
}
}
When I run this application, I get the following error:
System.InvalidOperationException: 'Cannot consume scoped service 'SmartWay.Services.IExceptionService' from singleton 'Microsoft.Extensions.Options.IConfigureOptions`1[Microsoft.AspNetCore.Mvc.MvcOptions]'.'
If I change IExceptionServcise's lifetime to transient than I have to do so for
Dbcontext, then for DbContextOptions... It seems that it isn't right way..
So, How can I solve this problem?
For resolving scoped service from singleton service, try _serviceProvider.CreateScope.
Follow steps below:
ExceptionService
public interface IExceptionService
{
void Save(Exception ex);
}
public class ExceptionService : IExceptionService
{
private readonly IServiceProvider _serviceProvider;
public ExceptionService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Save(Exception ex)
{
using (var scope = _serviceProvider.CreateScope())
{
var _context = scope.ServiceProvider.GetRequiredService<MVCProContext>();
_context.Add(new Book() { Title = ex.Message });
_context.SaveChanges();
}
}
}
Startup.cs
services.AddSingleton<IExceptionService, ExceptionService>();
services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();

How to pass dependencies into a controller from a custom middleware?

I have a custom middleware from which I want to add a scoped dependency.
public class MyMiddleware {
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext,
IOptionsSnapshot<ApiClientHttpSettings> settings,
IServiceCollection services)
{
services.AddScoped<ICustomer>(new Customer());
await _next(httpContext);
}
}
So that I can get it inside controllers:
public class CustomerController : ControllerBase
{
public ControllerBase(ICustomer customer)
{
}
}
But in the middleware IServiceCollection cannot be resolved.
I want to do this because there is a lot of logic to resolve the DI involved.
I can also try to do inside ConfigureServices but then I wont get access to IOptionsSnapshot<SupplyApiClientHttpSettings> settings I need for every request.
Any pointer to right direction is really appreciated.
I can also try to do inside ConfigureServices, but then I wont get access to IOptionsSnapshot<SupplyApiClientHttpSettings> settings I need for every request.
Here is how you can get access to IOptionsSnapshot inside a custom service. The full source is here in GitHub.
Create your settings class.
public class SupplyApiClientHttpSettings
{
public string SomeValue { get; set; }
}
Add a value for it in configuration (e.g. in appsettings.json).
{
"someValue": "Value from appsettings"
}
Define your service and inject IOptionsSnapshot into it.
public class CustomerService
{
private readonly SupplyApiClientHttpSettings settings;
public CustomerService(IOptionsSnapshot<SupplyApiClientHttpSettings> options)
{
this.settings = options.Value;
}
public Customer GetCustomer()
{
return new Customer
{
SomeValue = settings.SomeValue
};
}
}
Wire together your configuration, options, and service in Startup.
public class Startup
{
IConfiguration Configuration;
public Startup()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SupplyApiClientHttpSettings>(Configuration);
services.AddScoped<CustomerService>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
Inject the service into your controller. Use the service to get the customer with the up to date options snapshot.
public class CustomerController : Controller
{
private readonly CustomerService customerService;
public CustomerController(CustomerService customerService)
{
this.customerService = customerService;
}
public IActionResult Index()
{
return Json(customerService.GetCustomer());
}
}
Here is the full source in GitHub.
The answer was quite simple and close. Following is just what I had to do :
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ICustomer>(provider => {
var settings = Configuration.GetSection("ApiClientHttpSettings").Get<ApiClientHttpSettings>();
return new Customer(settings.Name, settings.Age);
});
}
The above ticks all the boxes for me :
New instance for each request
Able to read updated config at the time of request
Create instance according to custom logic

Autofac DI does not work as expected in Console Application

I have a console application that Autofac DI is used to inject data and service layer from web application project.
here is the setup on console application:
public static class ContainerConfig
{
public static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<DbFactory>().As<IDbFactory>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<Application>().As<IApplication>();
builder.RegisterType<DataRepository>().As<IDataRepository>();
builder.RegisterType<DataService>().As<IDataService>();
return builder.Build();
}
}
public interface IApplication
{
void Run();
}
public class Application : IApplication
{
private readonly IDataService _dataService;
public Application(IDataService dataService)
{
_dataService = dataService;
}
public void Run()
{
var data = _dataService.GetDataById(1);
var task = new TestTask("test");
data.AddTask(task);
_dataService.Update(data);
_dataService.SaveChanges();
}
}
main Program class:
class Program
{
static void Main(string[] args)
{
var container = ContainerConfig.Configure();
using (var scope = container.BeginLifetimeScope())
{
var app = scope.Resolve<IApplication>();
app.Run();
}
}
}
When the application is run loading the data works fine. However, saving a new entry does not seem to do the work.
However, when I remove DI and use simple class initializing in the Run method as below the save works fine:
IDbFactory dbFactory = new DbFactory();
IDataRepository dataRepository = new DataRepository(dbFactory);
var unitOfWork = new UnitOfWork(dbFactory);
IDataService service = new DataService(dataRepository, unitOfWork);
var data = service.GetDataById(1);
var task = new TestTask("test");
data.AddTask(task);
service.Update(data);
service.SaveChanges();
Am I missing something while I setup the autofac? It seems to access the data fine but when it comes to save it does not save the data. I debugged to see any issue but the program runs fine with no error. How can I debug this sort of issues to find more details?
Updated
public interface IDataService
{
void Add(TestTask task);
void SaveChanges();
}
public class DataService : IDataService
{
private readonly IDataRepository _dataRepository;
private readonly IUnitOfWork _unitOfWork;
public DataService(IDataRepository dataRepository, IUnitOfWork unitOfWork)
{
_dataRepository = dataRepository;
_unitOfWork = unitOfWork;
}
public void Add(TestTask task)
{
_dataRepository.Add(task);
}
public void SaveChanges()
{
_unitOfWork.Commit();
}
}
public class UnitOfWork : IUnitOfWork
{
private readonly IDbFactory _dbFactory;
private ApplicationDbContext _dbContext;
public UnitOfWork(IDbFactory dbFactory)
{
this._dbFactory = dbFactory;
}
public ApplicationDbContext DbContext => _dbContext ?? (_dbContext = _dbFactory.Init());
public void Commit()
{
DbContext.Commit();
}
}
After reading autofac scopes here
I found out that default scope is Instance Per Dependency. Which means that a unique instance will be returned from each request for a service. DbFactory should be for InstancePerLifetimeScope.
So changing configuration below fixes the issue:
public static class ContainerConfig
{
public static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<Application>().As<IApplication>();
builder.RegisterType<DataRepository>().As<IDataRepository>();
builder.RegisterType<DataService>().As<IDataService>();
return builder.Build();
}
}

Categories