I have created a hub in my Web API. It is very simple:
public class DashboardHub : Hub
{
public async Task SendMessage(InfoSummary infoSummary)
{
await Clients.All.SendAsync("ReceiveMessage", infoSummary);
}
}
I am trying to send a message to the Hub from a controller in the same Web API when data is updated.
I have seen 100 different answers, and nothing works. Basically my hub object in my controller is null, and I can't seem to get it instantiated.
private readonly IRepository _repo;
private readonly Helpers.Convert _convert;
private readonly CoreContext _context;
private readonly IMapper _mapper;
private readonly NotifyService _service;
private readonly DashboardHub _hub;
public MyController(IRepository repo,
CoreContext context,
IMapper mapper)
{
_convert = new Helpers.Convert(repo, mapper);
_repo = repo;
_context = context;
_mapper = mapper;
_hub = new DashboardHub();
_service = new NotifyService(_hub);
}
[HttpPost("updatestatus")]
public async Task<IActionResult> UpdateStatus(Header header) {
var returnVal = await _repo.ChangeStatus(header.HeaderId, header.Status);
headerSummary = _convert.ToReturnStatusHeader( await _repo.GetHeader(header.HeaderId));
// await _service.SendNotificationAsync(headerSummary);
await _hub.SendMessage(headerSummary);
return Ok(returnVal);
}
I have the
services.AddSignalR();
services.AddScoped(typeof(DashboardHub));
and
endpoints.MapHub<DashboardHub>("/Hubs/DashboardHub");
in the proper sections in the startup.cs file
I know I am missing something very small, but I would love to know what it is.
I have also tried creating a strongly typed hub, but that introduced even more problems.
Thanks in advance.
You have done there or four mistakes.
You don't need this line to be in your ConfigureServices method of Startup.cs. services.AddScoped(typeof(DashboardHub));
Remove it. Just keep services.AddSignalR();
Why are you using new key word, since .net core provide in-built dependency
injection service. Remove below lines.
_hub = new DashboardHub();
_service = new NotifyService(_hub);
Instead create a new interface INotifyService.cs for NotifyService.cs.
Register this service in ConfigureServices method of Startup.cs.
services.AddScoped<INotifyService, NotifyService>();
Your MyController.cs should be like below
Add this line.
using Microsoft.AspNetCore.SignalR;
private readonly IRepository _repo;
private readonly Helpers.Convert _convert;
private readonly CoreContext _context;
private readonly IMapper _mapper;
private readonly INotifyService _service;
private readonly IHubContext<DashboardHub> _hubContext
public MyController(IRepository repo, CoreContext context, IMapper mapper,INotifyService service,IHubContext<DashboardHub> hubContext)
{
_convert = new Helpers.Convert(repo, mapper);
_repo = repo;
_context = context;
_mapper = mapper;
_service = service;
_hubContext = hubContext;
}
[HttpPost("updatestatus")]
public async Task<IActionResult> UpdateStatus(Header header) {
var returnVal = await _repo.ChangeStatus(header.HeaderId, header.Status);
headerSummary = _convert.ToReturnStatusHeader( await _repo.GetHeader(header.HeaderId));
// await _service.SendNotificationAsync(headerSummary);
await hubContext.Clients.All.SendAsync("ReceiveMessage", headerSummary);
return Ok(returnVal);
}
Use same concept if you are sending messages inside your NotifyService.cs.
Well, I feel like a complete and utter newb. The fix is very simple. You must add the using statement telling the controller you want to use SignalR. OMG.. I am almost too embarrassed to put this up, but hope it will help someone else.
FIX:
using Microsoft.AspNetCore.SignalR;
:facepalm
What you could do is inject your hub using dependency injection in your controller. You can't just instanciate the hub in the controller like you are doing, and I would change it to a Singleton also.
services.AddSingleton(typeof(DashboardHub));
internal DashboardHub DashboardHub
{
get
{
return this.HttpContext.RequestServices.GetRequiredService<DashboardHub>();
}
}
Related
All - I have got two questions regarding the repository pattern used in the Ardalis CleanArchitecture.
With ListAsync how can I project to only a few fields not to all of them? Example: if I have 20+ fields in the table & I need only 3 fields to fetch from it. I see in the template the repository uses "ToListAsync()" ef api, however this executes a "Select * " in the SQL Server database table. I wanted only to fetch 3 fields which is part of my DTO.
With specification pattern I see an option to select required fields. That means when using specification I can project to a DTO. Where is the ideal place to put these kinds of dtos? Under specifications folder itself?
Any help on these would be very much appreciated. Thanks a bunch!
Template Repo: https://github.com/ardalis/CleanArchitecture
The easiest way I thought of is using AutoMapper. AutoMapper has an extension method IQueryable.ProjectTo<>. You can implement RepositoryBase<> and inherit that to EfRepository<> instead of inheriting RepositoryBase<> directly.
public class EfRepository<T> : ApplicationRepository<T>, IReadRepository<T>, IRepository<T> where T : EntityBase, IAggregateRoot
{
public EfRepository(ApplicationDbContext dbContext) : base(dbContext)
{
}
public EfRepository(ApplicationDbContext dbContext, IMapper mapper)
: base(dbContext, mapper)
{}
}
ApplicationRepository class
public abstract class ApplicationRepository<T> : RepositoryBase<T> where T : EntityBase, IAggregateRoot
{
private readonly IMapper _mapper;
private readonly DbContext _dbContext;
protected ApplicationRepository(DbContext dbContext)
: base(dbContext)
{
_dbContext = dbContext;
}
protected ApplicationRepository(DbContext dbContext, ISpecificationEvaluator specificationEvaluator)
: base(dbContext, specificationEvaluator)
{
_dbContext = dbContext;
}
protected ApplicationRepository(DbContext dbContext,
IMapper mapper)
: base(dbContext)
{
_dbContext = dbContext;
_mapper = mapper;
}
public async Task<List<TProjectTo>> ListAsyncProjected<TProjectTo>(CancellationToken cancellationToken = default)
{
return await ListAsyncProjected<TProjectTo>(null, cancellationToken);
}
public async Task<List<TProjectTo>> ListAsyncProjected<TProjectTo>(ISpecification<T> specification,
CancellationToken cancellationToken = default)
{
if (specification == null)
return await _dbContext.Set<T>()
.AsNoTracking()
.ProjectTo<TProjectTo>(_mapper.ConfigurationProvider)
.ToListAsync(cancellationToken);
return await SpecificationEvaluator.Default
.GetQuery(_dbContext.Set<T>()
.AsNoTracking()
.AsQueryable(), specification)
.ProjectTo<TProjectTo>(_mapper.ConfigurationProvider)
.ToListAsync(cancellationToken);
}
}
I've taken the project I'm currently working on and I have to be deploying soon, however I'm having some issues with WCF. I have the RestService and the IRestService and when I make some test calls everything is working fine. However I want to use some of the data services I have created so I could query the database in order to perform CRUD operations. Here is what I have in the Service:
public readonly ITimesheetService timesheetService;
public readonly IProjectService projectService;
public readonly IUserService userService;
public readonly INotificationService notificationsService;
public readonly IDepartmentService departmentService;
public readonly IUserTokenService userTokenService;
public TimesheetRestService(ITimesheetService timesheetService, IProjectService projectService, IUserService userService, INotificationService notificationService, IDepartmentService departmentService, IUserTokenService userTokenService)
{
this.timesheetService = timesheetService;
this.projectService = projectService;
this.userService = userService;
this.notificationsService = notificationService;
this.departmentService = departmentService;
this.userTokenService = userTokenService;
}
public TimesheetRestService()
{
}
I had to add constructor without parameters otherwise it's not working. And then I have the following 2 methods:
public string[] NewMethod()
{
string[] data = new string[] { "grant_type", "p_username", "p_password" };
return data;
}
public IEnumerable<DepartmentServiceModel> GetDepertment()
{
string userId = GetUserId();
if (userId == null)
{
return null;
}
var deparments = this.departmentService.GetDepartments(userId).ToList().AsQueryable().To<DepartmentServiceModel>().ToList();
return deparments;
}
The NewMethod() is there just for testing purposes. My problem is with the GetDepartment() method as the departmentService and all the other services are null.
The developer before me has been using ninject, so I have tried adding the following code to the RegisterServices(IKernel kernel):
kernel.Bind(b => b.From(Assemblies.RestService).
SelectAllClasses().
BindDefaultInterface());
kernel.Bind<ITimesheetRestService>().To<TimesheetRestService>();
However when the TimeSheetRestService class is being initialized - the constructor with no parameters is being called. How can I call the constructor with the other services so I could use them to pull data from the database?
Got following error, when i try to use my context into a custom service :
System.ObjectDisposedException : 'Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context
My code is composed of a controller and a service.
The (simplified) code of my controller is :
public class IcsController : Controller
{
public string current_Directory = System.IO.Directory.GetCurrentDirectory();
//Importation du context (BDD)
private readonly Sync4All_AngularContext _context;
private readonly UserManager<User> _userManager;
public readonly OverbookingsManager _overbookingsManager;
private IIcsService _icsService;
public IcsController(Sync4All_AngularContext context, UserManager<User> userManager, IIcsService icsService, OverbookingsManager overbookingsManager)
{
_context = context;
_userManager = userManager;
_icsService = icsService;
_overbookingsManager = overbookingsManager;
}
//---------------------------------Telechargement et Update de tous les ICALS-----------------------------------
[HttpGet]
public ActionResult DownloadAndUpdate()
{
_overbookingsManager.SendEmailsOverbookings();
return Ok();
}
}
The service is defined as followed in configuration services
services.AddScoped<OverbookingsManager>();
And the code of the service is as below :
public class OverbookingsManager
{
private readonly Sync4All_AngularContext _context;
public OverbookingsManager(Sync4All_AngularContext context)
{
_context = context;
}
public async void SendEmailsOverbookings()
{
List<Overbookings> overbookinsList = await _context.Overbookings.Where(m => DateTime.Compare(m.DtEmailSent, DateTime.Today.AddDays(-7)) < 0).ToListAsync(); //This is where i got the error of context disposed
//blabla rest of my code
}
}
When i do a get request on my controller, it calls the methode DownloadAndUpdate(), which use the service.
Others methodes of my controller and services use _context, but i never do _context.dispose().
I don't understand the problem, could you please help ?
Thanks
The problem is void:
public async void SendEmailsOverbookings()
Replace that with Task:
public async Task SendEmailsOverbookings()
For some explanation take a look at the documentation:
For methods other than event handlers that don't return a value, you
should return a Task instead, because an async method that returns
void can't be awaited.
I have ASP.NET MVC 5 (.NET Framework) app with Entity Framework 6.2.
I'm using NUnit and Moq frameworks for testing.
I need to test if roleResult.Succeeded is true when RoleExists() extension(static) method returns false for "User" role.
HomeController.cs :
private ApplicationDbContext _db;
private RoleManager<IdentityRole> _roleManager;
private ApplicationUserManager _userManager;
// Hard Entity Framework dependency (working application)
public HomeController()
{
_db = new ApplicationDbContext();
_roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(_db));
_userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(_db));
if (!_roleManager.RoleExists("User"))
{
IdentityResult roleResult = _roleManager.Create(new IdentityRole("User"));
}
}
// Dependency injection only for testing
public HomeController(ApplicationDbContext context, ApplicationUserManager userManager, RoleManager<IdentityRole> roleManager, string role) // DI for testing
{
_db = context;
_roleManager = roleManager;
_userManager = userManager;
if (!_roleManager.RoleExists(role))
{
IdentityResult roleResult = _roleManager.Create(new IdentityRole(role));
}
}
HomeTest.cs :
[Test]
public void HomeController_RoleDoesNotExist_AddRole()
{
var mockContext = new Mock<ApplicationDbContext>();
var mockUserManager = new Mock<ApplicationUserManager>();
var mockRoleManager = new Mock<RoleManager<IdentityRole>>();
mockRoleManager.Setup(x => x.RoleExists("User")).Returns(false); // Moq says: nope :<
var homeController = new HomeController(mockContext.Object, mockUserManager.Object, mockRoleManager.Object, "User");
// Some Assert that I really don't know to deal with.
}
First: I don't know how implement testing code responsible for getting roleResult.Succeeded value.
Second: Moq cannot setup static methods and I don't know how to deal with it in my situation. I'll probably need to replace private properties in my HomeController class with some new interfaces but then my Hard Entity Framework dependency will stop working because of for example: _db = new ApplicationDbContext() cannot be converted to IMyNewAppDbContext.
Thanks for help.
I have a multi-layer application that I started writing in ASP.NET Core 1.1 which I'm still learning along the way. I have organized it like previous apps I've done in the Web API, I have host service (net core app), business layer and data layer that is above database. Business and data layers were net core standard libraries, but when I wanted to add entity framework I had to modify data layer to look like net core app, so now I have Startup.cs with configurations there. That allowed me to configure entity framework service and to create migrations in the data layer. But now I have a problem as I wanted to add asp.net identity. Every tutorial on the net is about SPAs that have everything in one project.
I have added identity to Startup.cs and database is generated well
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddEntityFramework(connectionString);
services.AddMyIdentity();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// User settings
options.User.RequireUniqueEmail = true;
});
}
public void Configure(IApplicationBuilder app)
{
app.UseIdentity();
}
but now I need to use UserManager from a class that is not a Controller and I don't know how to deal with dependency injection.
To explain better, I have an Account controller in my Host Service
[HttpPost]
[Route("Register")]
public async Task<IActionResult> Register([FromBody]RegisterUserDto dto)
{
var result = await Business.Commands.Accounts.Register(dto);
return Ok(result);
}
Business layer just calls the Data layer
public async static Task<ResponseStatusDto> Register(RegisterUserDto dto)
{
// some code here
var identityLogon = await Data.Commands.ApplicationUsers.Register(dto);
// some code here as well
return new ResponseStatusDto();
}
Now the question is, how do I get UserManager in the Data Register method? It's a simple class, it doesn't inherit from a controller, dependency injection is not working for constructors like in the examples found here
Core Identity
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private static bool _databaseChecked;
private readonly ILogger _logger;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
}
//
// GET: /Account/Login
So, how do I pass UserManager that is configured in Startup to some random class somewhere in the middleware? I have seen this question, but the answer to just pass null values to UseManager constructor is not working nor I think it's good.
//EDIT as per Set's answer
I have removed all static references, but I'm still not quite there. I have followed this dependency injection instructions, but I'm not sure how to instantiate and call Add method.
I have created an interface
public interface IIdentityTransaction
{
Task<IdentityResult> Add(ApplicationUser appUser, string password);
}
and implemened it
public class IdentityTransaction : IIdentityTransaction
{
private readonly ApplicationDbContext _dbContext;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public IdentityTransaction(ApplicationDbContext context, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
_roleManager = roleManager;
_userManager = userManager;
_dbContext = context;
}
public async Task<IdentityResult> Add(ApplicationUser applicationUser, string password)
{
return await _userManager.CreateAsync(applicationUser, password);
}
}
then I injected it to a service collection in Startup.cs
services.AddScoped<IIdentityTransaction, IdentityTransaction>();
but how to call Add method from IdentityTransaction service?
I cannot instantiate it nor use dependency injection on constructor as it just loops my problem. #Set mentioned
or pass UserManager userManager as parameter to method
pass it from where?
I think I'm very close, but I'm missing something.
I have tried using
IIdentityTransaction it = services.GetRequiredService<IIdentityTransaction>();
but services which is IServiceProvider is null, I don't know where to get it from either.
DI in ASP.NET Core works the same for controller and non-controller classes using "constructor injection" approach.
You have the problem as Register method is static, so doesn't have access to instance variables/properties. You need to
make Register method non-static
or pass UserManager<ApplicationUser> userManager as parameter to method
In general, you should avoid using static classes for business logic as they don't help to test your code properly and produce the code coupling. Search via internet/SO and you will find a lot of topics why static is bad.
Use DI to get the instance of Data.Commands.ApplicationUsers class in your controller. If you need only one instance of this class for your application - use singleton lifetime for it.
Update. Again, use constructor injection: modify your "Data Layer" class so it can get the instance of IIdentityTransaction as constructor parameter:
public class YourDataLayerClass : IYourDataLayerClass
{
private IIdentityTransaction _identityTransaction;
public YourDataLayerClass(IIdentityTransaction identityTransaction)
{
_identityTransaction = identityTransaction;
}
public void MethodWhereYouNeedToCallAdd()
{
_identityTransaction.Add(...);
}
}
And idea the same for IYourDataLayerClass instance: register dependency
services.AddScoped<IYourDataLayerClass, YourDataLayerClass>();
and then the class (middleware in your case, if I understand you properly) that depends on it should receive that instance via constructor:
public class YourMiddleware
{
private IYourDataLayerClass _yourDataLayerClass;
public YourMiddleware(IYourDataLayerClass yourDataLayerClass)
{
_yourDataLayerClass = yourDataLayerClass;
}
...
}
Yes you are very close.
First thing, either remove context parameter from the IdentityTransaction constructor as in your code snipped it appears to be useless. Or if you plan to use it later, declare it in the DI container:
services.AddScoped<ApplicationDbContext, ApplicationDbContext>();
Second thing, you simply need to add IIdentityTransaction as a dependency in the controller's constructor, and remove SignInManager and UserManager from its dependencies as eventually you won't use these directly within the controller:
public class AccountController : Controller
{
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private static bool _databaseChecked;
private readonly ILogger _logger;
IIdentityTransaction _identityTransaction;
public AccountController(
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory,
IIdentityTransaction identityTransaction)
{
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
_identityTransaction = identityTransaction;
}
If you need an additional business layer (IBusinessLayer) between the controller, same process, declare the class in the DI container at startup, add IIdentityTransaction as a dependency in the business class constructor, and update the controller's dependencies from IIdentityTransaction to IBusinessLayer.
A couple more precisions.
services.AddScoped<IIdentityTransaction, IdentityTransaction>();
This piece of code does NOT inject instances or dependencies. It declares an interface and its associated implementation in the DI container, so it can be injected later when required. Actual instances are injected when the objects that required them are actually created. I.e. the controller gets its dependencies injected when it is instantiated.
IIdentityTransaction it = services.GetRequiredService<IIdentityTransaction>();
What you tried to do here is called the dependency locator pattern, and is often considered as an anti-pattern. You should stick to dependency injection via the constructor, it's much cleaner.
The key is to declare everything in the DI container at startup, even your custom business/data layers classes, never instantiate them yourself anymore, and declare them as required dependencies in any classes' constructor that need them.