Umbraco ApplicationEventHandler Not Firing For User - umbraco

I am trying to send an email to the administrator when a new user is created in the CMS. But when I create a new user while debugging in VS, the first breakpoint at "umbraco.BusinessLogic.User.New += User_New;" is never hit. I am using version 7.3.4 of Umbraco.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web
using Umbraco.Core;
using umbraco.BusinessLogic;
namespace NewUserEmail
{
/// <summary>
/// Summary description for NewUserNotification
/// </summary>
public class NewUserNotification : ApplicationEventHandler
{
public NewUserNotification()
{
umbraco.BusinessLogic.User.New += User_New;
}
private void User_New(umbraco.BusinessLogic.User sender, EventArgs e)
{
System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage();
message.To.Add("nw#email.com");
message.Subject = "This is the Subject line";
message.From = new System.Net.Mail.MailAddress("From#online.microsoft.com");
message.Body = "This is the message body";
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("yoursmtphost");
smtp.Send(message);
}
}
}

I think because you are using ApplicationEventHandler, you'll want to override the ApplicationStarted method instead of use your constructor.
The way I used needs using Umbraco.Core.Services;.
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//I usually use Members for things like this but if you want user, it'll be UserService.SavedUser +=...
MemberService.Saved += User_New;
}
private void User_New(IMemberService sender, EventArgs e)
{
System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage();
message.To.Add("nw#email.com");
message.Subject = "This is the Subject line";
message.From = new System.Net.Mail.MailAddress("From#online.microsoft.com");
message.Body = "This is the message body";
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("yoursmtphost");
smtp.Send(message);
}
Further reading here.

Related

How to get messages from Azure Service Bus (Queue) in View.cshtml in ASP.NET MVC web app

I have a console application with which I can get messages from Azure Service Bus (Queue).
using System;
using System.Text;
using System.Text.Json;
using Microsoft.Azure.ServiceBus;
using SampleShared.Models;
namespace SampleAppReceiver
{
class Program
{
const string connString = "<my_connection_string>";
static IQueueClient qClient;
static async Task Main(string[] args)
{
qClient = new QueueClient(connString, "<my_queue_name>");
var msgOptions = new MessageHandlerOptions(ExceptionReceivedHandler)
{
// How many messages we can process at time
MaxConcurrentCalls = 1,
// need to wait until a message is fully processed
AutoComplete = false,
};
qClient.RegisterMessageHandler(ProcessMessageAsync, msgOptions);
Console.ReadLine();
await qClient.CloseAsync();
}
private static async Task ProcessMessageAsync(Message msg, CancellationToken token)
{
// Deserialise the msg body
var jsonBody = Encoding.UTF8.GetString(msg.Body);
var personObj = JsonSerializer.Deserialize<Person>(jsonBody);
Console.WriteLine($"Login: {personObj.Login}");
Console.WriteLine($"Message: {personObj.Message}");
// Updating the queue that the message has been processed sucsessfully
await qClient.CompleteAsync(msg.SystemProperties.LockToken);
}
private static Task ExceptionReceivedHandler(ExceptionReceivedEventArgs args)
{
Console.WriteLine($"Something went wrong, {args.Exception}");
return Task.CompletedTask;
}
}
}
How can I correctly add all the received messages to View.cshtml from controller?
Now I have a service (C# interface) with which I can send messages from View.cshtml to Azure Service Bus (queue):
// ...
public interface IAzureBusService
{
Task SendMessageAsync(Person personMessage, string queueName);
}
// ...
Controller method:
[HttpPost]
public async Task<IActionResult> Index(Person person)
{
await _busService.SendMessageAsync(person, "personqueue");
return RedirectToAction("Index");
}
Create a service Bus in Azure portal.
Create a Queue as per the below screenshot.
I followed the below steps in displaying the queue messages in a view.
You can use the console application reference in your MVC project to display queue messages in a View by calling the method of fetching the messages from queue.
You need to use the below code in the controller class.
public ActionResult Index()
{
List<QueueMsgs> queMsglist = new List<QueueMsgs>();
QueueMsgs msgs = new QueueMsgs();
queMsglist = GetMessagesFromQueue();
return View(queMsglist);
}
public void GetMessagesFromQueue()
{
ServiceBusReceiver receiver = new ServiceBusReceiver();
receiver.Listener();
}
public void Listener()
{
ServiceBusConnectionStringBuilder conStr;
QueueClient client;
try
{
conStr = new ServiceBusConnectionStringBuilder(QueueAccessKey);
client = new QueueClient(conStr, ReceiveMode.ReceiveAndDelete, RetryPolicy.Default);
var messageHandler = new MessageHandlerOptions(ListenerExceptionHandler)
{
MaxConcurrentCalls = 1,
AutoComplete = false
};
client.RegisterMessageHandler(ReceiveMessageFromQ, messageHandler);
}
catch (Exception exe)
{
Console.WriteLine("{0}", exe.Message);
Console.WriteLine("Please restart application ");
}
public async Task ReceiveMessageFromQ(Message message, CancellationToken token)
{
string result = Encoding.UTF8.GetString(message.Body);
Console.WriteLine("Message received from Queue = {0}", result);
await Task.CompletedTask;
}
public Task ListenerExceptionHandler(ExceptionReceivedEventArgs exceptionReceivedEventArgs)
{
Console.WriteLine("{0}", exceptionReceivedEventArgs.Exception);
return Task.CompletedTask;
}
#model IEnumerable<MVC.Models.QueueMsgs>
#{
ViewBag.Title = "Queue Messages";
}
#foreach (var item in Model)
{
<div>
#item.message
<hr />
</div>
}
Displaying Queue Messages in View.

MissingMethodException: No parameterless constructor defined for type

I have a Blazor app using .net core 5. I am trying to run a page to trigger an email send (EmailSample) which works fine until I try to add a constructor injection for logging errors. as soon as I add the constructor injection and rerun the page I get a No parameterless constructor defined for type error. Am I not using this correctly? How to fix so I don't get that error. I could get around this if I could figure out how to add logger as a service in Startup.cs/ConfigureServices/services.AddSingleton< ??? >
and then just do the [Inject] in the EmailSample class but can't figure out how.
[Inject]
public ILogger _logger { get; set; }
The code that causes the error is... (Note: set up as a partial class, not in the .razor page #code{})
public partial class EmailSample
{
private readonly ILogger<EmailSample> _logger;
public EmailSample (ILogger<EmailSample> logger)
{
_logger = logger;
}
[Inject]
public IMailService _mailService { get; set; }
public void SendEmail()
{
string emailTo = "test#test.com";
string emailFrom = "noreply#test.com";
string emailSubject = "This is a test email.";
string emailType = "html";
string emailCc = string.Empty;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("Hello" + " " + emailTo);
sb.AppendLine("</br>");
sb.AppendLine("This is a test of the email functionality");
sb.AppendLine("</br>");
sb.AppendLine("Best Regards,");
sb.AppendLine("</br>");
sb.AppendLine("Signature");
string emailBody = sb.ToString();
try
{
throw new Exception("This is an email sample exception test");
//Task tSendEmail = _mailService.SendEmailAsync(emailTo, emailSubject, emailBody, emailFrom, emailCc, emailType);
//if (tSendEmail.IsCompletedSuccessfully)
//{
// _statusMessage = "The email has been sent successfully.";
//}
//else
//{
// _statusMessage = "Failed to send email successfully.";
//}
}
catch (Exception ex)
{
_logger.LogError(ex, "The email for { emailto } failed to send.", emailTo);
}
}
public void OnGet()
{
_statusMessage = "";
}
}
[Inject]
private ILogger<EmailSample> logger {get; set;}
Don't forget the namespace
Microsoft.Extensions.Logging

How do you inject a repo into a service in ASP.Net Core MVC

I have a really odd problem.
I have defined a Repository for a DbSet Called PageHits.
This is the interface:
public interface IPageHitRepo
{
IQueryable<PageHit> PageHits { get; }
void AddPageHit(PageHit pageHit);
void SaveChanges();
}
This is the implementation:
public class PageHitRepo : IPageHitRepo
{
private CtasContext _context;
public PageHitRepo(CtasContext context)
{
_context = context;
}
public IQueryable<PageHit> PageHits => _context.PageHits;
public void AddPageHit(PageHit pageHit)
{
_context.Add(pageHit);
}
public void SaveChanges()
{
_context.SaveChanges();
}
}
I configure in startup.ConfigureServices like this:
services.AddScoped<IPageHitRepo, PageHitRepo>();
This is all just normal stuff. I use this to inject into controllers all the time using constructor injection.
But right now I want to inject this repo service into another service called PageHitService.
It has a lot going on but the injection part should be simple.
I have a IPageHitService interface defined like this:
public interface IPageHitService
{
// Default implimentation overridden in PageHitService.
Task Invoke(IServiceProvider sp) => Task.FromResult(true);
Task EmailSiteHitNotification(PageHit pageHit, IServiceProvider sp);
Task LogPageHitToDB(PageHit pageHit, IServiceProvider sp);
}
Then the top of the PageHitService looks like this:
public class PageHitService : IPageHitService
{
private IPageHitRepo _pageHitRepo;
public PageHitService(IPageHitRepo pageHitRepo)
{
_pageHitRepo = pageHitRepo;
}
public async Task Invoke(IServiceProvider sp)
{
HttpContext httpContext =
sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
IWebHostEnvironment env = sp.GetRequiredService<IWebHostEnvironment>();
IConfiguration config = sp.GetRequiredService<IConfiguration>();
// _context = sp.GetRequiredService<CtasContext>();
bool visitSet =
!string.IsNullOrEmpty(httpContext.Session.GetString("VisitSet"));
and here is the registration in StartUp.ConfigureServices:
services.AddScoped<IPageHitService, PageHitService>();
But when I try to compile I get this error:
There is no argument given that corresponds to the required formal parameter 'pageHitRepo' of 'PageHitService.PageHitService(IPageHitRepo)'
Why do we not get this error in constructors?
The way I understand DI in .Net Core is it looks at the constructor in the controller and if it sees a dependency it resolves it by the registration in startup and then if it sees a dependency in the service it is resolving it resolves that service's dependency and so on.
Why is it giving me this error.
I understand if I make a default paramterless constructor the error goes away but then ASP.Net Core DI does not know which constructor to use and both constructors get hit and my page ends up with a 500 error and cannot be shown.
What am I missing here?
Update 1:
Don't know if this can help.
But in answer to Steve's comment below I do have a BaseController class and a Base PageModel.
public class BaseController : Controller
{
public BaseController(IServiceProvider sp)
{
IPageHitService pageHitService = sp.GetRequiredService<IPageHitService>();
// pageHitService.Invoke(sp).Await();
pageHitService.Invoke(sp).Wait();
}
#region Callbacks if you use AsyncMethod.Await() extension method.
private void Completed()
{
Console.WriteLine("Completed pageHitService.Invoke from BaseController");
}
private void HandleError(Exception ex)
{
// TODO: Log Error to DB;
}
#endregion
}
public class BasePageModel : PageModel
{
public BasePageModel(IServiceProvider sp)
{
IPageHitService pageHitService = sp.GetRequiredService<IPageHitService>();
// pageHitService.Invoke(sp).Await();
pageHitService.Invoke(sp).Wait();
}
#region Callbacks if you use AsyncMethod.Await() extension method.
private void Completed()
{
Console.WriteLine("Completed pageHitService.Invoke from BaseController");
}
private void HandleError(Exception ex)
{
// TODO: Log Error to DB;
}
#endregion
}
But I can't see this affecting the constructor and causing an error there.
Update 2
I found the offending code in the PageHitService:
PageHitService pageHitService = new PageHitService();
Invoke was static because I was fooling around with going the factory route.
And I could not call the non-static methods so I had to instantiate an instance to call the 2 methods in the API.
Nothing is static now so I am going to comment out that line and just try to call the methods straight up.
Here is the full class:
public class PageHitService : IPageHitService
{
private IPageHitRepo _pageHitRepo;
public PageHitService(IPageHitRepo pageHitRepo)
{
_pageHitRepo = pageHitRepo;
}
public async Task Invoke(IServiceProvider sp)
{
HttpContext httpContext =
sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
IWebHostEnvironment env = sp.GetRequiredService<IWebHostEnvironment>();
IConfiguration config = sp.GetRequiredService<IConfiguration>();
// _context = sp.GetRequiredService<CtasContext>();
bool visitSet =
!string.IsNullOrEmpty(httpContext.Session.GetString("VisitSet"));
PageHit PageHit = new PageHit
{
SessionID = httpContext.Session.Id,
UserIP = httpContext.Connection.RemoteIpAddress.ToString(),
EnvironmentName = env.EnvironmentName,
Url = httpContext.Request.PathBase + httpContext.Request.Path,
ReferrerUrl = httpContext.Request.Headers["Referer"].ToString(),
DateTime = DateTime.Now
};
PageHitService pageHitService = new PageHitService();
bool logPageHitsOn = bool.Parse(config["LogPageHitsOn"]);
bool emailSiteHitNotificationsOn = bool.Parse(config["EmailSiteHitNotificationsOn"]);
if (!visitSet)
{
// create session
httpContext.Session.SetString("VisitSet", "true");
// Emails (1st visit of session)
if (emailSiteHitNotificationsOn)
{
await pageHitService.EmailSiteHitNotification(PageHit, sp);
}
}
// Logs all PageHits to DB.
if (logPageHitsOn)
{
await pageHitService.LogPageHitToDB(PageHit, sp);
}
}
public async Task EmailSiteHitNotification(PageHit pageHit,
IServiceProvider sp)
{
IEmailService emailService = sp.GetRequiredService<IEmailService>();
StringBuilder body = new StringBuilder();
body.AppendFormat($"<b>SessionID:</b> {pageHit.SessionID}");
body.Append("<br /><br />");
body.AppendFormat($"<b>User Ip:</b> {pageHit.UserIP}");
body.Append("<br /><br />");
body.AppendFormat($"<b>Environment:</b> {pageHit.EnvironmentName}");
body.Append("<br /><br />");
body.AppendFormat($"<b>Url (Page Hit):</b> {pageHit.Url}");
body.Append("<br /><br />");
body.AppendFormat($"<b>ReferrerUrl:</b> {pageHit.ReferrerUrl}");
body.Append("<br /><br />");
body.AppendFormat($"<b>DateTime:</b> {pageHit.DateTime}");
body.Append("<br /><br />");
string subject = "CTAS Hit Notification";
await emailService.SendEmail(body.ToString(), subject);
}
public async Task LogPageHitToDB(PageHit pageHit,
IServiceProvider sp)
{
CtasContext context = sp.GetRequiredService<CtasContext>();
await context.PageHits.AddAsync(pageHit);
await context.SaveChangesAsync();
}
}

trying to implement session per web request, No current session context configure

As I said in the title I want to implement session per web request.
My session provider is configured like this (I'm not interested in changing this conf.)
public class SessionProvider
{
public static SessionProvider Instance { get; private set; }
private static ISessionFactory _SessionFactory;
static SessionProvider()
{
var provider = new SessionProvider();
provider.Initialize();
Instance = provider;
}
private SessionProvider()
{
}
private void Initialize()
{
string csStringName = "ConnectionString";
var cfg = Fluently.Configure()
....ommiting mappings and db conf.
.ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
.BuildConfiguration();
_SessionFactory = cfg.BuildSessionFactory();
}
public ISession OpenSession()
{
return _SessionFactory.OpenSession();
}
public ISession GetCurrentSession()
{
return _SessionFactory.GetCurrentSession();
}
}
Inside Global.asax.cs I have following code related to session per web req.
private static ISessionFactory SessionFactory { get; set; }
protected void Application_Start()
{
SessionFactory = MyDomain.SessionProvider.Instance.OpenSession().SessionFactory;
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
}
protected void Application_EndRequest(object sender, EventArgs e)
{
var session = CurrentSessionContext.Unbind(SessionFactory);
session.Dispose();
}
On debuggin my webapp I get error: No current session context configured.
ErrorMessage referenced to global.asax line CurrentSessionContext.Bind(session);
Updated
Added .ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
and now I have error message on retrieving data from my controller like this
Error message: Session is closed! Object name: 'ISession'.
Controller code:
using (ISession session = SessionProvider.Instance.GetCurrentSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
data = session.QueryOver<Object>()
...ommiting
transaction.Commit();
return PartialView("_Partial", data);
}
}
1st Question
You need to configure it in your nhibernate configuration section:
<property name="current_session_context_class">web</property>
Also I currently do something like this:
if (!CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
2nd Question
Please look at the following article to modify configuration fluently: currentsessioncontext fluent nhibernate how to do it?
3rd Question
You are closing your session twice.
using (ISession session = SessionProvider.Instance.GetCurrentSession())
closes your session. Then you did it again in Application_EndRequest. If you have another question please post a new question.

recommendation pattern to use for handling sessions per web requests in mvc using NHibernate

For example.
My session factory is located in MyDomain.SessionProvider class.
Session can be open using ISession session = SessionProvider.Instance.OpenSession()
Step: SessionProvider.cs
public static SessionProvider Instance { get; private set; }
private static ISessionFactory _SessionFactory;
static SessionProvider()
{
var provider = new SessionProvider();
provider.Initialize();
Instance = provider;
}
private SessionProvider()
{
}
private void Initialize()
{
string csStringName = "ConnectionString";
var cfg = Fluently.Configure()
//ommiting mapping and db conf.
.ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
.BuildConfiguration();
_SessionFactory = cfg.BuildSessionFactory();
}
public ISession OpenSession()
{
return _SessionFactory.OpenSession();
}
public ISession GetCurrentSession()
{
return _SessionFactory.GetCurrentSession();
}
Step: Global.asax.cs
public static ISessionFactory SessionFactory { get; private set; }
Application Start
SessionFactory = SessionProvider.Instance.OpenSession().SessionFactory;
App_BeginRequest
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
EndRequest
dispose session
var session = CurrentSessionContext.Unbind(SessionFactory);
session.Dispose();
Step3.HomeController
I should be using current session like
var session = SessionProvider.Instance.GetCurrentSession();
using (var tran = session.BeginTransaction())
{
//retrieve data from session
}
Now, with trying to retrieve data on my controller like desc. in Step3. I got error message that my session is closed. I tried to remove Application_EndRequest block inside global.asax cause my transaction is wrapped with session but with no success. Still same error.
Second/side question: is this pattern accepted widely, or it is better to wrapped inside custom attributes on mvc controllers. Thanks.
Updated:
On my controller when try to instantiate current session in line
var session = SessionProvider.Instance.GetCurrentSession();
I'm getting following error:
**Connection = 'session.Connection' threw an exception of type 'NHibernate.HibernateException'**
**base {System.ApplicationException} = {"Session is closed"}**
Thanks #LeftyX
I solved this problem using TekPub video Mastering NHibernate with some customizations.
Global.asax
//Whenever the request from page comes in (single request for a page)
//open session and on request end close the session.
public static ISessionFactory SessionFactory =
MyDomain.SessionProvider.CreateSessionFactory();
public MvcApplication()
{
this.BeginRequest += new EventHandler(MvcApplication_BeginRequest);
this.EndRequest +=new EventHandler(MvcApplication_EndRequest);
}
private void MvcApplication_EndRequest(object sender, EventArgs e)
{
CurrentSessionContext.Unbind(SessionFactory).Dispose();
}
private void MvcApplication_BeginRequest(object sender, EventArgs e)
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
protected void Application_Start()
{
SessionFactory.OpenSession();
}
and inside my controller
var session = MvcApplication.SessionFactory.GetCurrentSession();
{
using (ITransaction tx = session.BeginTransaction())
{... omitting retrieving data}
}
You can find a couple of simple and easy implementations here and here and find some code here.
I like Ayende's approach to keep everything simple and clean:
public class Global: System.Web.HttpApplication
{
public static ISessionFactory SessionFactory = CreateSessionFactory();
protected static ISessionFactory CreateSessionFactory()
{
return new Configuration()
.Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hibernate.cfg.xml"))
.BuildSessionFactory();
}
public static ISession CurrentSession
{
get{ return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
protected void Global()
{
BeginRequest += delegate
{
CurrentSession = SessionFactory.OpenSession();
};
EndRequest += delegate
{
if(CurrentSession != null)
CurrentSession.Dispose();
};
}
}
In my projects I've decided to use a IoC container (StructureMap).
In case you're interested you can have a look here.

Resources