HangFire Queued Emails "Succeeding" But not Sending - c#

I can send emails using the method when not using hangfire.
When the method is called from hangfire it is shown as succeeded but the email doesnt send.
BackgroundJob.Schedule(
() => Email(Subject),
TimeSpan.FromMinutes(1)
);
public void Email(string Subject)
{
try
{
//Initialize WebMail
WebMail.SmtpServer = "relay.example.com";
WebMail.SmtpPort = 587;
WebMail.SmtpUseDefaultCredentials = false;
WebMail.UserName = "xxxx";
WebMail.Password = "xxxx";
WebMail.From = "\"Reports\" <Reports#example.com>";
//Send Email
WebMail.Send(to: "client#example.com",
subject: Subject,
body: "Test Email"
);
}
catch (Exception e)
{
}
}
The username, password, and relay service have been removed for security as has the actual email addresses.
I have gone and removed the try/catch blocks to see if there are any errors produced.
Normally, It succeeds as before but occasionally it will throw the following error.
System.NullReferenceException
Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.WebPages.Scope.AspNetRequestScopeStorageProvider.get_RequestScopeInternal()
at System.Web.WebPages.Scope.AspNetRequestScopeStorageProvider.get_CurrentScope()
at System.Web.Helpers.WebMail.set_SmtpServer(String value)
at Email(String Subject)
I have then attempted the version that allows for type passing.
BackgroundJob.Schedule<WebMail>(
() => Email(Subject),
TimeSpan.FromMinutes(1)
);
However that throws a compiler error about using a static type.
Thanks for any help guys.
EDIT:
Still open to better options. However I did find a cheeky work around. I was able to get this to work by creating a method that fires a post request to a page to actually send the email. Whereas Hangfire just activates the post request.
EDIT: NVM workaround was a false positive due to an extra line that was left in from testing

The WebMail class is intended for usage in ASP.NET Web Pages context. When used outside that context it probably doesn't have everything it needs, and thus you will get a NullReferenceException.
Instead, use the SmtpClient class.

Related

How do I send SignalR messages from within an existing Azure function without adding a new Azure function? Can it be done?

C# learner here. I hope the question makes sense, but if not, read on!
I have an existing Azure function setup (.NET 6) that, when it receives a http trigger, will trigger an orchestrator function that uses an activity trigger to start a function that will copy all messages from an Azure Storage Queue to A Cosmos DB (the QueueStore function below). I would like to also send each of the messages to a client using SignalR via an existing SignalR Service and Hub, also in Azure.
There is a lot of documentation on creating the SignalR and negotiate functions but how do I send a message from within my already called function?
The code for the copy function is below. I'm sure those more experienced developers will spot lots of ways I can optimise things, but I am honestly just happy getting it to work at this stage. As it currently stands, the function works as expected and required but I want to add the SignalR functionality at the commented location in the code.
How can I best go about this?
[FunctionName(nameof(QueueStore))]
public static async Task<string> QueueStore([ActivityTrigger] QueueName queue, ILogger log)
{
// Get the connection string
string connectionString = Environment.GetEnvironmentVariable("QueueStorage");
try
{
CosmosClient client = new CosmosClient("some info here");
Database database = client.GetDatabase("database");
bool databaseExists = true;
try
{
var response = await database.ReadAsync();
}
catch (CosmosException ex)
{
if (ex.StatusCode.Equals(HttpStatusCode.NotFound))
{
// Does not exist
databaseExists = false;
}
}
//Instantiate a QueueClient which will be used to manipulate the queue
QueueClient queueClient = new QueueClient(connectionString, queue.Name);
QueueProperties properties = await queueClient.GetPropertiesAsync();
bool appDisconnected = false;
//string message = "Stored messages ";
if (queueClient.Exists() && databaseExists)
{
Container container = await database.CreateContainerIfNotExistsAsync(id: queue.Name,
partitionKeyPath: "/partKey", //name of the json var we want as partition key
throughput: 400
);
while (appDisconnected == false)
{
if (queueClient.GetProperties().Value.ApproximateMessagesCount == 0)
{
Thread.Sleep(100);
}
else
{
QueueMessage[] retrievedMessage = await queueClient.ReceiveMessagesAsync(1);
var fd = JsonConvert.DeserializeObject<JObject(retrievedMessage[0].Body.ToString());
if (!fd.ContainsKey("disconnected"))
{
PartitionKey partKey = new PartitionKey(queue.PartKey);
// save to db
var createdItem = await container.CreateItemAsync<JObject>(
item: fd,
partitionKey: partKey);
//######## HERE IS WHERE I WANT TO SEND THE fd Object via SignalR
//######## I have tried many different things but nothing works
await queueClient.DeleteMessageAsync(retrievedMessage[0].MessageId,
retrievedMessage[0].PopReceipt);
}
else
{
appDisconnected = true;
}
}
}
return "Copied all Items";
}
else
{
return $"The queue peek didn't work because I can't find the queue:-(";
}
}
catch (Exception ex)
{
return ex.Message;
}
}
I have tried calling a SignalR function from the orchestrator but that is adding a function outside the copy function process and would mean duplicating the calls to the queue and doesn't really help. I haven't seen any way to just send a SignalR message from the location indicated in the code. I have also tried standard .Net SignalR code, but can find no examples that have worked for me. Any pointers and suggestions would be greatly received.
Can this be done? Should I just create an entirely new function app and make a http call to that?
Trying to make my intended version of this has been causing me a lot of issues and, having not found any documentation on it, I may be architecting things wrong but thought I would ask here for any suggestions before re-writing everything as I am rather time-limited to work on this app.
Thanks in advance for any help!
I have tried adding an example Azure SignalR Function called by the orchestrator and also called from the commented area of the code and I have tried examples from the .NET documentation and the SignalR Azure Functions examples. I was hoping that there were examples of this or a tutorial somewhere I could follow but it seems like I'm trying to do something no one else has done, which might mean I'm barking up the wrong tree entirely... :-(

LibGit2SharpException while fetching: 'too many redirects or authentication replays'

I've got a problem while trying to fetch from my repository using libgit2sharp.
I'm trying to fetch from my Github repository. It is cloned via https. As soon as the fetch command runs, the LibGit2SharpException throws saying: "too many redirects or authentication replays".
I've read a few other questions here on so, but none of them helped so far. Using DefaultCredentials only leads to a 401 error, which makes sense.
Does anybody know how to solve this one?
using Repository repo = new Repository(#"path\to\repo");
Remote remote = repo.Network.Remotes["origin"];
IEnumerable<string> refSpec = remote.FetchRefSpecs.Select(x => x.Specification);
Credentials credentials = new UsernamePasswordCredentials { Username = "USER", Password = "PASS" };
Credentials Handler(string url, string fromUrl, SupportedCredentialTypes types) => credentials;
FetchOptions options = new FetchOptions { CredentialsProvider = Handler };
string log = "Fetching remote";
Commands.Fetch(repo, remote.Name, refSpec, options, log);
Edit: As far as I figured out, the problem is, that it's a private repo, since I can fetch public ones from github. Any ideas on how to solve it?
I solved this issue with using a PersonalAccessToken from Github. The Token then can be used as the password of the credentials

Difficulty calling method from Hang Fire

I have a controller method which is responsible for scheduling appointments. I'm looking to schedule a task to remind the user via SMS (twilio) 5 minutes before their appointment to login to the service.
Here is my method call:
private SMSNotifier sms = new SMSNotifier();
BackgroundJob.Schedule( () => sms.SendPatientUpcomingApptNotificationTest(appointment), appointment.Start.AddMinutes(-5));
and here is my class:
public SMSNotifier()
{
var accountSid = ConfigurationManager.AppSettings["TwilioSid"];
// Your Auth Token from twilio.com/console
var authToken = ConfigurationManager.AppSettings["TwilioToken"]; ;
TwilioClient.Init(accountSid, authToken);
}
public void SendPatientUpcomingApptNotificationTest(Appointment appt)
{
var message = MessageResource.Create(
to: new PhoneNumber("+xxxxxxxxxx"),
from: new PhoneNumber("+xxxxxxxxxx"),
body: string.Format("Hello {0} {1}! You have an upcoming web based video appointment with xxxxxxxxxx. Please login to the website to be seen. Your appointment time is: {2} Thank you - xxxxxxxx", "xxxxxxx", "xxxxxxxx", appt.Start));
}
}
I keep getting this error:
Server Error in '/' Application.
Self referencing loop detected for property 'User' with type 'System.Data.Entity.DynamicProxies.AspNetUser_80E6332CC002F8FCF589159A68E74A0922BEE992586B9FE280D950E149CCC7EB'. Path 'Patient.ActiveSessions[0]'.
But I just don't understand why. I don't reference a User object anywhere.
I should mention I obviously googled that error:
JSON.NET Error Self referencing loop detected for type
Self referencing loop detected - Getting back data from WebApi to the browser
∞
None of this provided any progress. I still have the same error.
What do you guys think?
I definitely think it has to do with the way I'm trying to schedule the 'SendPatientUpcomingApptNotificationTest' method. I can do a Console.WriteLine and it queues the job fine.
IE:
BackgroundJob.Schedule( () => Console.WriteLine("TestSchedule"), appointment.Start.AddMinutes(-5));
Works perfectly
It sounds like it's running into this error when trying to serialize your appointment object.
When you schedule a background job, Hangfire saves info about the method you are calling and its arguments to its job store. If you're passing a complex object as an argument to the method, it attempts to serialize the whole object graph to json.
The recommended approach is to keep your job arguments small and simple. For example, pass an appointmentId to the job instead of the whole appointment:
BackgroundJob.Schedule( () => sms.SendPatientUpcomingApptNotificationTest(appointmentId), ...);
Then you would retrieve the actual appointment within the job.

CRM Context Object persists connection even when creating new instance

I have a system in place for a WCF Service, in which I take in some credentials from the client. I then try to authenticate with CRM using these credentials. If the authentication fails, I use a pre-defined service account, with the credentials stored in web.config.
What I have found is, no matter what, the first set of credentials used persists for any further requests, no matter how much I tear down the first object. I even instantiate new objects, wrap each context in a using statement, etc.
I have watered the code down into a simple 'connect, retry' block, and this suffers the same issue. The code is as follows:
try
{
var connection = new CrmConnection();
connection.ServiceUri = new Uri("https://my.crm.dynamics.com/");
connection.ClientCredentials = new ClientCredentials();
connection.ClientCredentials.UserName.UserName = "removed1";
connection.ClientCredentials.UserName.Password = "removed1";
using (var crm = new CrmOrganizationServiceContext(connection))
{
var req = new Microsoft.Crm.Sdk.Messages.WhoAmIRequest();
var resp = (Microsoft.Crm.Sdk.Messages.WhoAmIResponse)crm.Execute(req);
}
}
catch (Exception ex) { }
try
{
var connection = new CrmConnection();
connection.ServiceUri = new Uri("https://my.crm.dynamics.com/");
connection.ClientCredentials = new ClientCredentials();
connection.ClientCredentials.UserName.UserName = "removed2";
connection.ClientCredentials.UserName.Password = "removed2";
using (var crm = new CrmOrganizationServiceContext(connection))
{
var req = new Microsoft.Crm.Sdk.Messages.WhoAmIRequest();
var resp = (Microsoft.Crm.Sdk.Messages.WhoAmIResponse)crm.Execute(req);
}
}
catch (Exception ex) { }
Assume that removed1 is incorrect and removed2 is correct. The second call will fail instantly with a token exception, saying invalid credentials. If removed1 is correct, and removed2 is not, the first will authenticate and get the WhoAmIRequest fine. Then, removed2 should fail, but it does not, as it seems to still hold the connection using the old credentials. The invalid credentials still allows the service to make requests. Not good!
The bizarre thing is, the code for the authentication is in a separate project. I have included this project in a simple console application, and everything works fine. I can only assume this is something to do with the WCF service and the way it holds connections. I've tried manually disposing, calling garbage collection, setting to null, etc. I've also tried using web config connection strings called by name (hard coded 2 test ones), tried manually creating the connection string settings with unique names, using CrmConnection.Parse(), etc.
I have even copy pasted the code i'm using directly into a console application, and it works fine. Due to this, I am convinced it is to do with the behavior of a WCF service, and not the code itself. I set the class to have the behavior of
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
But no luck. If it is of any importance, this code is running in a message inspector class which implements IDispatchMessageInspector.
How can I ensure that I can get a fresh session? Thanks.
You are using the default constructor of the CrmConnection class. When doing that, your connection is cached by name. This name is supposed to be the name of the ConnectionStringSettings, but using this constructor that property is never being set and keeps its default value, thus always returning the first connection object created.
Just use another overload of the constructors, e.g. that using a connection string or accepting the service url, credentials etc.
The CrmConnection class was designed to offer an easy way to create connectionstrings in config files, similar to database connection strings. It had its issues and has been removed from the Dynamics CRM 2016 SDK.

Can a UserNamePasswordValidator throw anything other than a MessageSecurityException?

I have a WCF service hooked up with a UserNamePasswordValidator through my web.config, no problem there. In my validator, I override Validate, check the credentials and throw a FaultException if necessary.
Example:
public class CredentialValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "dummy")
{
throw new FaultException<AuthenticationFault>(new AuthenticationFault(), "Invalid credentials supplied");
}
}
}
If I consume this service myself in a .NET application and provide invalid credentials, a MessageSecurityException is thrown with the following message:
"An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail."
The FaultException I had expected is the InnerException of the MessageSecurityException.
Is there any way to have the client receive just the FaultException?
The MessageSecurityException isn't particularly descriptive concerning the true cause of the exception (a quick search on SO yields a variety of problems including server/client time sync..), and since a third party will be consuming the service, I'd like to be as clear as possible.
I had the very same issue some months ago and after some research I came to the conclusion that you may throw whatever you want from validator code, but client will still get MessageSecurityException, which contains no useful information at all.
We had to however give the client to know what actually happened -
1. wrong username/password
2. password expired, change needed
3. some other custom application specific states
So we changed CredentialValidator logic in a way that it throws exception only in case 1. In other cases it would actually allow the real WCF method to be called, but there we would check also for password expiration etc. and in case of some problems throw FaultException already from method body.
In our case this worked well for only service with this type of validation was log-in service - so the client would always know why exactly he wasn't authenticated.
From custom password validator you can return FaultCode that describe what's wrong:
throw new FaultException("Invalid user name or bad password.", new FaultCode("BadUserNameOrPassword"));
throw new FaultException("Password expired.", new FaultCode("PasswordExpired"));
throw new FaultException("Internal service error.", new FaultCode("InternalError"));
Throw the error as MessageSecurityException with inner exception as FaultException
public override void Validate(string userName, string password)
{
var isValid = ValidateUser(userName, password);
if (!isValid)
{
throw new MessageSecurityException("Userid or Password is invalid", new FaultException("Userid or Password is invalid"));
}
}

Categories