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