I have wrapped the C# FCM AdminSDK in a WCF. When I publish the code to my local using debug everything works as expected. When I publish the code using release I get a "Object reference not set to an instance of an object." when attempting to instantiate the "Message" object. Why does this happen?
The exception happens on the line "var fcmMessage = new Message()"
using FirebaseAdmin;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
using ID.Service.PushNotification.Enums;
using ID.Service.PushNotification.Models;
using ID.Service.PushNotification.ServiceHelpers;
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Hosting;
namespace ID.Service.PushNotification.Helpers
{
public class FcmHelper
{
readonly static FirebaseApp app = FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromFile(HostingEnvironment.MapPath(#"~/App_Data/jq4bb-37597f7301.json"))
});
public static void BulkPushNotification(List<EnrolmentModel> enrolments, string message, int messageId, DeepLink path = DeepLink.None)
{
foreach (EnrolmentModel enrolment in enrolments)
{
PushNotification(enrolment, message, messageId, path);
}
}
public static async void PushNotification(EnrolmentModel enrolment, string message, int messageId, DeepLink path = DeepLink.None)
{
try
{
var pathLink = (path != DeepLink.None) ? path.GetPath() : "";
var registrationToken = Encoding.UTF8.GetString(Convert.FromBase64String(enrolment.DeviceToken));
LogHelper.Error("rt: " + registrationToken);
LogHelper.Error("msg: " + message);
LogHelper.Error("pl" + pathLink);
var fcmMessage = new Message()
{
Token = registrationToken,
Android = new AndroidConfig()
{
Notification = new AndroidNotification()
{
Body = message,
Title = "Title",
Sound = "bing"
//ClickAction = "rewards",
//Color = "#CA5151",
//Icon="",
},
Priority = Priority.Normal,
TimeToLive = TimeSpan.FromSeconds(2419200),
//Data = new Dictionary<string, string>()
//{
// { "deepLinkPath", pathLink }
//},
}
};
// Send a message to the device corresponding to the provided
// registration token.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(fcmMessage);
bool successfullySent = false;
if (response.ToLower().Contains("projects/com-app/messages/0:"))
{
successfullySent = true;
}
ResultFeedbackServiceHelper.SaveResultFeedback(
response,
Convert.ToInt32(messageId),
Convert.ToInt32(enrolment.DeviceId),
successfullySent,
new List<string> { enrolment.DeviceToken }
);
}
catch (Exception ex)
{
ResultFeedbackServiceHelper.SaveResultFeedback(
ex.Message,
Convert.ToInt32(messageId),
Convert.ToInt32(enrolment.DeviceId),
false,
new List<string> { enrolment.DeviceToken }
);
LogHelper.Error("Error sending push messages to (fcm) gcn " + ex.ToString());
}
}
}
}
Exception:''2019-03-05 15:09:55,637 Thread:'[13]' Level:'ERROR' Message:'Error sending push messages to (fcm) gcn System.NullReferenceException: Object reference not set to an instance of an object.
at ID.Service.PushNotification.Helpers.FcmHelper.d__2.MoveNext() in D:\BuildAgents\Agent1_work\475\s\PNS\Main\ID.Service.PushNotification\Helpers\FCMHelper.cs:line 49'
Related
I'm trying to make a service on a server that runs every 24 hours to get information from Blancco reports, then input it into a Filemaker database.
The service works completely as it should but every time it ends, I get the meesage box error:
Error 1053: the service did not respond to the start or control request in a timely fashion
I tried to run service on my pc and I don't get any errors there. So I guess there might be something wrong with the server, but I don't know what.
Hopefully one of you guys know
code:
using System;
using System.ServiceProcess;
namespace Blancco_Report_Service
{
class Program
{
static void Main(string[] args)
{
Service service = new Service();
if (Environment.UserInteractive)
{
service.RunAsConsole(args);
}
else
{
ServiceBase[] services;
services = new ServiceBase[] { service };
ServiceBase.Run(services);
}
}
}
}
I know it looks weird I made it create a batch file to get the reports, but I could not get Blancco's API to work with RestSharp so this was the next best option. This is not important anyways
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Xml;
using Formatting = Newtonsoft.Json.Formatting;
namespace Blancco_Report_Service
{
class Service : ServiceBase
{
private string fmToken = "placeholder";
Thread work;
public void RunAsConsole(string[] args)
{
OnStart(args);
Console.ReadLine();
OnStop();
}
protected override void OnStart(string[] args)
{
work = new Thread(new ThreadStart(Work));
work.Start();
}
protected override void OnStop()
{
Console.WriteLine("Stopping");
try
{
work.Abort();
}
catch (Exception)
{
}
}
private void Work()
{
List<Report> reports = GetReports();
foreach (var report in reports)
{
UpdateField(report);
}
}
private List<Report> GetReports()
{
List<Report> reports = new List<Report>();
StringBuilder output = new StringBuilder();
XmlDocument doc = new XmlDocument();
Process process = new Process();
Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory + "export.bat");
ProcessStartInfo startInfo = new ProcessStartInfo("cmd.exe", "/C \"" + AppDomain.CurrentDomain.BaseDirectory + "export.bat\"");
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
process.StartInfo = startInfo;
process.OutputDataReceived += (sender, args) => output.Append(args.Data);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
string[] outputSplit = output.ToString().Split("web-service-request.xml\" ");
try
{
doc.LoadXml(outputSplit[1]);
}
catch (Exception)
{
Console.WriteLine("No reports");
return reports;
}
string json = JsonConvert.SerializeXmlNode(doc, Formatting.None, true);
json = json.Substring(45);
dynamic result = JObject.Parse(json);
foreach (var report in result.report)
{
Report reportClass = new Report();
try
{
dynamic blancco_data = report.blancco_data;
dynamic blancco_hardware_report = blancco_data.blancco_hardware_report;
dynamic hardware_entries = blancco_hardware_report.entries;
foreach (var hardwere_entry in hardware_entries)
{
if (hardwere_entry["#name"] == "system")
{
dynamic system_entry = hardwere_entry.entry;
foreach (var systemInfo in system_entry)
{
if (systemInfo["#name"] == "serial")
{
reportClass.Serial = systemInfo["#text"];
}
}
}
}
dynamic description = blancco_data.description;
dynamic document_log = description.document_log;
dynamic log_entry = document_log.log_entry[0];
string date = log_entry.date;
DateTime oDate = DateTime.ParseExact(date, "MM/dd/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
date = oDate.ToString();
reportClass.Date = date;
reports.Add(reportClass);
}
catch (Exception e)
{
}
}
return reports;
}
private void UpdateField(Report report)
{
var client = new RestClient(FMAPI.Url + "_find");
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("authorization", "Bearer " + fmToken);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{ \"query\": [{ \"service::ser_serial no\": \"=" + report.Serial + "\"}] }", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
dynamic result = JObject.Parse(response.Content);
string code = result.messages[0].code;
switch (code)
{
case "952":
fmToken = FMAPI.GetToken("service_000_DK");
UpdateField(report);
return;
case "401": return;
default: break;
}
string recordID = result.response.data[0].recordId;
client = new RestClient(FMAPI.Url + "records/" + recordID);
request = new RestRequest(Method.PATCH);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "application/json");
request.AddHeader("authorization", "Bearer " + fmToken);
request.AddParameter("application/json", "{ \"fieldData\": { \"service::ser_dataerased\": \"" + report.Date + "\" } }", ParameterType.RequestBody);
response = client.Execute(request);
result = JObject.Parse(response.Content);
code = result.messages[0].code;
if (code == "0")
{
Console.WriteLine("Uploaded");
}
else
{
Console.WriteLine("Failed to Upload");
}
}
}
}
I'm attempting to initiate a call with the Microsoft Graph SDK Create call API using the code sample below. The attempt fails with a Not Found exception.
I have registered the bot application, added the API call permissions and I am able to receive incoming calls from Teams.
It's not clear from the Microsoft documentation whether Teams users can be called directly or whether they have to be allocated a VoIP number. Has anyone been able to use the Graph SDK to call a Teams User? Is there some special configuration a User needs to have in order to be able to receive a call?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Graph.Communications.Common.Telemetry;
using Microsoft.Extensions.Logging;
using Microsoft.Graph;
using Microsoft.Graph.Communications.Calls;
using Microsoft.Graph.Communications.Calls.Media;
using Microsoft.Graph.Communications.Client;
using Microsoft.Skype.Bots.Media;
namespace sipbotcaller
{
class Program
{
private static string APP_NAME = "";
private static string APP_ID = "";
private static string APP_SECRET = "";
private static string TENANT_ID = "";
private static string CALLBACK_URI = "";
private static string CERTIFICATE_THUMBPRINT = "";
private static int MEDIA_PORT = 10000;
private static string PUBLIC_IP = "";
private static string HOSTNAME = "";
static async Task Main(string[] args)
{
Console.WriteLine("Teams Call Console:");
GraphLogger graphLogger = new GraphLogger(APP_NAME);
graphLogger.DiagnosticLevel = System.Diagnostics.TraceLevel.Verbose;
ILogger logger = new ConsoleLogger(graphLogger);
AuthenticationProvider authProvider = new AuthenticationProvider(
APP_NAME,
APP_ID,
APP_SECRET,
TENANT_ID,
graphLogger);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var users = await graphClient.Users.Request().GetAsync();
foreach (var user in users)
{
Console.WriteLine($"user Id: {user.Id}.");
Console.WriteLine($"user Display Name: {user.DisplayName}.");
}
var mediaPlatformSettings = new MediaPlatformSettings()
{
MediaPlatformInstanceSettings = new MediaPlatformInstanceSettings()
{
CertificateThumbprint = CERTIFICATE_THUMBPRINT,
InstanceInternalPort = MEDIA_PORT,
InstancePublicIPAddress = IPAddress.Parse(PUBLIC_IP),
InstancePublicPort = MEDIA_PORT,
ServiceFqdn = HOSTNAME,
},
ApplicationId = APP_ID,
};
var builder = new Microsoft.Graph.Communications.Client.CommunicationsClientBuilder(
APP_NAME,
APP_ID,
graphLogger);
builder
.SetAuthenticationProvider(authProvider)
.SetNotificationUrl(new Uri(CALLBACK_URI))
.SetMediaPlatformSettings(mediaPlatformSettings)
.SetServiceBaseUrl(new Uri(CALLBACK_URI));
var client = builder.Build();
AudioSocketSettings audioSockSettings = new AudioSocketSettings {
CallId = Guid.NewGuid().ToString(),
SupportedAudioFormat = AudioFormat.Pcm16K,
StreamDirections = StreamDirection.Sendrecv
};
AudioSocket audioSock = new AudioSocket(audioSockSettings);
var mediaConfig = MediaPlatform.CreateMediaConfiguration(audioSock);
Console.WriteLine($"media config: {mediaConfig}");
Console.WriteLine($"Attempting to call {users.First().DisplayName}.");
var call = new Call
{
CallbackUri = CALLBACK_URI,
TenantId = TENANT_ID,
Targets = new List<InvitationParticipantInfo>()
{
new InvitationParticipantInfo
{
Identity = new IdentitySet
{
User = new Identity
{
DisplayName = users.First().DisplayName,
Id = users.First().Id
},
}
}
},
RequestedModalities = new List<Modality>()
{
Modality.Audio
},
MediaConfig = new AppHostedMediaConfig()
{
Blob = mediaConfig.ToString(Newtonsoft.Json.Formatting.None)
},
};
var callResult = await client.Calls().AddAsync(call);
Console.WriteLine($"Call result {callResult.Id}.");
Console.WriteLine("Finished.");
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
}
}
Result:
<snip>
StatefulCall: Verbose
StatefulCall: Info
StatefulCall: Verbose
StatefulCall: Info
StatefulCall: Info
StatefulCall: Error {
"error": {
"code": "itemNotFound",
"message": "Unexpected exception returned from the service.\r\nStatus Code: NotFound"
}
}
StatefulCall: Info
I have the following controller:
[Authorize]
public class SetupController : ApiController
{
[HttpPost]
public Task async SetupPartnerPackAsync(SetupInformation info)
{
if (info.SslCertificateGenerate)
{
SetupManager.CreateX509Certificate(info);
}
else
{
SetupManager.LoadX509Certificate(info);
}
info.SslCertificateThumbprint = SetupManager.GetX509CertificateThumbprint(info);
info.AzureAppKeyCredential = SetupManager.GetX509CertificateInformation(info);
await SetupManager.RegisterAzureADApplication(info);
}
}
But I have the following 2 error which seems simple:
Severity Code Description Project File Line Suppression State
Error CS1520 Method must have a return
type InnovationInABoxWebApi H:\InnovationInABoxWebApi\InnovationInABoxWebApi\Controllers\SetupController.cs 24 Active
Severity Code Description Project File Line Suppression State
Error CS4033 The 'await' operator can only be used within an async
method. Consider marking this method with the 'async' modifier and
changing its return type to
'Task'. InnovationInABoxWebApi H:\InnovationInABoxWebApi\InnovationInABoxWebApi\Controllers\SetupController.cs 39 Active
However I am not sure how to fix this, as the operation can take some time to complete, it really needs to be asybnc
and the setupmanager
using CERTENROLLLib;
using Microsoft.Identity.Client;
using Microsoft.Online.SharePoint.TenantAdministration;
using Microsoft.SharePoint.Client;
using Newtonsoft.Json;
using OfficeDevPnP.Core;
using OfficeDevPnP.Core.Entities;
using OfficeDevPnP.Core.Framework.Provisioning.Model;
using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers;
using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Resources;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;
namespace InnovationInABoxWebApi.Components
{
public static class SetupManager
{
public static String GetX509CertificateThumbprint(SetupInformation info)
{
var certificate = info.AuthenticationCertificate;
return (certificate.Thumbprint.ToUpper());
}
public static String GetX509CertificateInformation(SetupInformation info)
{
// var basePath = String.Format(#"{0}..\..\..\..\Scripts\", AppDomain.CurrentDomain.BaseDirectory);
var certificate = info.AuthenticationCertificate;
//var certificate = new X509Certificate2();
//if (info.SslCertificateGenerate)
//{
// certificate.Import($#"{basePath}{info.SslCertificateCommonName}.cer");
//}
//else
//{
// certificate = new X509Certificate2(info.SslCertificateFile, info.SslCertificatePassword);
//}
var rawCert = certificate.GetRawCertData();
var base64Cert = System.Convert.ToBase64String(rawCert);
var rawCertHash = certificate.GetCertHash();
var base64CertHash = System.Convert.ToBase64String(rawCertHash);
var KeyId = System.Guid.NewGuid().ToString();
var keyCredential =
"{" +
"\"customKeyIdentifier\": \"" + base64CertHash + "\"," +
"\"keyId\": \"" + KeyId + "\"," +
"\"type\": \"AsymmetricX509Cert\"," +
"\"usage\": \"Verify\"," +
"\"key\": \"" + base64Cert + "\"" +
"}";
return (keyCredential);
}
public static void CreateX509Certificate(SetupInformation info)
{
var certificate = CreateSelfSignedCertificate(info.SslCertificateCommonName.ToLower(),
info.SslCertificateStartDate, info.SslCertificateEndDate, info.SslCertificatePassword);
SaveCertificateFiles(info, certificate);
}
public static void LoadX509Certificate(SetupInformation info)
{
var certificate = new X509Certificate2(info.SslCertificateFile, info.SslCertificatePassword);
info.AuthenticationCertificate = certificate;
info.SslCertificateCommonName = certificate.SubjectName.Name;
}
public static void SaveCertificateFiles(SetupInformation info, X509Certificate2 certificate)
{
info.AuthenticationCertificate = certificate;
//var basePath = String.Format(#"{0}..\..\..\..\Scripts\", AppDomain.CurrentDomain.BaseDirectory);
//info.SslCertificateFile = $#"{basePath}{info.SslCertificateCommonName}.pfx";
//var pfx = certificate.Export(X509ContentType.Pfx, info.SslCertificatePassword);
//System.IO.File.WriteAllBytes(info.SslCertificateFile, pfx);
//var cer = certificate.Export(X509ContentType.Cert);
//System.IO.File.WriteAllBytes($#"{basePath}{info.SslCertificateCommonName}.cer", cer);
}
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName, DateTime startDate, DateTime endDate, String password)
{
// Create DistinguishedName for subject and issuer
var name = new CX500DistinguishedName();
name.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
// Create a new Private Key for the certificate
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft RSA SChannel Cryptographic Provider";
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.Length = 2048;
privateKey.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)";
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
privateKey.Create();
// Define the hashing algorithm
var serverauthoid = new CObjectId();
serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // Server Authentication
var ekuoids = new CObjectIds();
ekuoids.Add(serverauthoid);
var ekuext = new CX509ExtensionEnhancedKeyUsage();
ekuext.InitializeEncode(ekuoids);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, String.Empty);
cert.Subject = name;
cert.Issuer = cert.Subject;
cert.NotBefore = startDate;
cert.NotAfter = endDate;
cert.X509Extensions.Add((CX509Extension)ekuext);
cert.Encode();
// Enroll the certificate
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert);
string certData = enroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64HEADER);
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
certData, EncodingType.XCN_CRYPT_STRING_BASE64HEADER, String.Empty);
var base64encoded = enroll.CreatePFX(password, PFXExportOptions.PFXExportChainWithRoot);
// Instantiate the target class with the PKCS#12 data
return new X509Certificate2(
System.Convert.FromBase64String(base64encoded), password,
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable);
}
public async static Task RegisterAzureADApplication(SetupInformation info)
{
// Fix the App URL
if (!info.AzureWebAppUrl.EndsWith("/"))
{
info.AzureWebAppUrl = info.AzureWebAppUrl + "/";
}
// Load the App Manifest template
//Stream stream = typeof(SetupManager)
// .Assembly
// .GetManifestResourceStream("OfficeDevPnP.PartnerPack.Setup.Resources.azure-ad-app-manifest.json");
using (StreamReader sr = new StreamReader("Resources\azure-ad-app-manifest.json"))
{
// Get the JSON manifest
var jsonApplication = sr.ReadToEnd();
var application = JsonConvert.DeserializeObject<AzureAdApplication>(jsonApplication);
var keyCredential = JsonConvert.DeserializeObject<KeyCredential>(info.AzureAppKeyCredential);
application.displayName = info.ApplicationName;
application.homepage = info.AzureWebAppUrl;
application.identifierUris = new List<String>();
application.identifierUris.Add(info.ApplicationUniqueUri);
application.keyCredentials = new List<KeyCredential>();
application.keyCredentials.Add(keyCredential);
application.replyUrls = new List<String>();
application.replyUrls.Add(info.AzureWebAppUrl);
// Generate the Application Shared Secret
var startDate = DateTime.Now;
Byte[] bytes = new Byte[32];
using (var rand = System.Security.Cryptography.RandomNumberGenerator.Create())
{
rand.GetBytes(bytes);
}
info.AzureAppSharedSecret = System.Convert.ToBase64String(bytes);
application.passwordCredentials = new List<object>();
application.passwordCredentials.Add(new AzureAdApplicationPasswordCredential
{
CustomKeyIdentifier = null,
StartDate = startDate.ToString("o"),
EndDate = startDate.AddYears(2).ToString("o"),
KeyId = Guid.NewGuid().ToString(),
Value = info.AzureAppSharedSecret,
});
// Get an Access Token to create the application via Microsoft Graph
var office365AzureADAccessToken = await AzureManagementUtility.GetAccessTokenSilentAsync(
AzureManagementUtility.MicrosoftGraphResourceId,
ConfigurationManager.AppSettings["O365:ClientId"]);
var azureAdApplicationCreated = false;
// Create the Azure AD Application
try
{
await CreateAzureADApplication(info, application, office365AzureADAccessToken);
azureAdApplicationCreated = true;
}
catch (ApplicationException ex)
{
var graphError = JsonConvert.DeserializeObject<GraphError>(((HttpException)ex.InnerException).Message);
if (graphError != null && graphError.error.code == "Request_BadRequest" &&
graphError.error.message.Contains("identifierUris already exists"))
{
// We need to remove the existing application
// Thus, retrieve it
String jsonApplications = await HttpHelper.MakeGetRequestForStringAsync(
String.Format("{0}applications?$filter=identifierUris/any(c:c+eq+'{1}')",
AzureManagementUtility.MicrosoftGraphBetaBaseUri,
HttpUtility.UrlEncode(info.ApplicationUniqueUri)),
office365AzureADAccessToken);
var applications = JsonConvert.DeserializeObject<AzureAdApplications>(jsonApplications);
var applicationToUpdate = applications.Applications.FirstOrDefault();
if (applicationToUpdate != null)
{
// Remove it
await HttpHelper.MakeDeleteRequestAsync(
String.Format("{0}applications/{1}",
AzureManagementUtility.MicrosoftGraphBetaBaseUri,
applicationToUpdate.Id),
office365AzureADAccessToken);
// And add it again
await CreateAzureADApplication(info, application, office365AzureADAccessToken);
azureAdApplicationCreated = true;
}
}
}
if (azureAdApplicationCreated)
{
// TODO: We should upload the logo
// property mainLogo: stream of the application via PATCH
}
}
}
public static async Task CreateAzureADApplication(SetupInformation info, AzureAdApplication application, string office365AzureADAccessToken)
{
String jsonResponse = await HttpHelper.MakePostRequestForStringAsync(
String.Format("{0}applications",
AzureManagementUtility.MicrosoftGraphBetaBaseUri),
application,
"application/json", office365AzureADAccessToken);
var azureAdApplication = JsonConvert.DeserializeObject<AzureAdApplication>(jsonResponse);
info.AzureAppClientId = azureAdApplication.AppId.HasValue ? azureAdApplication.AppId.Value : Guid.Empty;
}
}
}
You are defining the method with async word after the return type Task, async must be before Task.
public async Task SetupPartnerPackAsync(SetupInformation info)
{
.
.
.
Recently i have decided to create a plugin for the TFS for tracking work item changes based on ISubscriber interface.
So the workflow would be the following:
1) Work item state changes
2) Plugin catches the WorkItemChangedEvent
3) Send an email to the person specified in the Requester
As the base for my project, i used the following project from CodePlex - The Mail Alert
After i adopted it to my needs and saved compiled binaries in %TFS-DIR%\Microsoft Team Foundation Server 14.0\Application Tier\Web Services\bin\Plugins the TFS restarted the tier and... that's it. On work item change the ProcessEvent method is not called, but it should be.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Server;
using System.Diagnostics;
using System.Xml.Linq;
using System.Xml;
using System.DirectoryServices;
using System.Net.Mail;
using System.Xml.Xsl;
using System.Configuration;
using System.Reflection;
namespace MailAlert
{
public class WorkItemChangedEventHandler : ISubscriber
{
static string serverPath = "";
static string ExternalURL = "";
static string MailAddressFrom = "";
static string SMTPHost = "";
static string Password = "";
static int Port = 25;
static int index = 0;
static string projectCollectionFolder;
static Uri projectCollectionUri;
static WorkItemStore wiStore;
static WorkItem wItem;
static WorkItemChangedEvent workItemChangedEvent;
static string teamProjectPath = "";
static VersionControlServer versionControlServer;
static TfsTeamProjectCollection projectCollection;
static Dictionary<IdentityDescriptor, TeamFoundationIdentity> m_identities = new Dictionary<IdentityDescriptor, TeamFoundationIdentity>(IdentityDescriptorComparer.Instance);
public Type[] SubscribedTypes()
{
return new Type[1] { typeof(WorkItemChangedEvent) };
}
public WorkItemChangedEventHandler()
{
TeamFoundationApplicationCore.Log("WorkItemChangedEvent Started", index++, EventLogEntryType.Information);
}
public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType,
object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
{
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: ProcessEvent entered", index++, EventLogEntryType.Information);
statusCode = 0;
properties = null;
statusMessage = String.Empty;
GetTfsServerName();
projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(TfsTeamProjectCollection.GetFullyQualifiedUriForName(serverPath));
try
{
if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent)
{
workItemChangedEvent = notificationEventArgs as WorkItemChangedEvent;
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: WorkItem " + workItemChangedEvent.WorkItemTitle + " was modified", index++, EventLogEntryType.Information);
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: serverPath - " + serverPath, index++, EventLogEntryType.Information);
projectCollectionFolder = requestContext.ServiceHost.VirtualDirectory.ToString();
projectCollectionUri = new Uri(serverPath + projectCollectionFolder);
projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(projectCollectionUri);
wiStore = projectCollection.GetService<WorkItemStore>();
versionControlServer = projectCollection.GetService<VersionControlServer>();
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: Before process workitem", index++, EventLogEntryType.Information);
ProcessWorkItem();
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: After process workitem", index++, EventLogEntryType.Information);
}
}
catch (Exception ex)
{
TeamFoundationApplicationCore.Log("WorkItemChangedEventHandler: FUCKING EXCEPTION! =>\n" + ex.Message, index++, EventLogEntryType.Error);
}
return EventNotificationStatus.ActionPermitted;
}
private static void GetTfsServerName()
{
try
{
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
XmlDocument XmlDoc = new XmlDocument();
XmlDoc.Load(assemblyFolder + #"\Settings.xml");
// Declare the xpath for finding objects inside the XML file
XmlNodeList XmlDocNodes = XmlDoc.SelectNodes("/configuration/tfssettings");
XmlNodeList XmlDocExt = XmlDoc.SelectNodes("/configuration/Externaltfssettings");
// Define a new List, to store the objects we pull out of the XML
serverPath = XmlDocNodes[0].InnerText;
ExternalURL = XmlDocExt[0].InnerText;
XmlNodeList XmlDocNodes2 = XmlDoc.SelectNodes("/configuration/appSettings");
foreach (XmlNode mailNode in XmlDocNodes2)
{
foreach (XmlNode varElement in mailNode.ChildNodes)
{
switch (varElement.Attributes["key"].Value)
{
case "MailAddressFrom":
MailAddressFrom = varElement.Attributes["value"].Value;
break;
case "SMTPHost":
SMTPHost = varElement.Attributes["value"].Value;
break;
case "Password":
Password = varElement.Attributes["value"].Value;
break;
case "Port":
Port = Convert.ToInt32(varElement.Attributes["value"].Value);
break;
}
}
}
}
catch (Exception ex)
{
EventLog.WriteEntry("WorkItemChangedEventHandler", ex.Message);
}
}
public string Name
{
get { return "WorkItemChangedEventHandler"; }
}
public SubscriberPriority Priority
{
get { return SubscriberPriority.High; }
}
private static void ProcessWorkItem()
{
var teamProjects = versionControlServer.GetAllTeamProjects(false);
for (int i = 0; i < teamProjects.Length; i++)
{
string teamProjectName = teamProjects[i].Name;
var teamProject = teamProjects[i];
Project teamProjectWI = wiStore.Projects[i];
teamProjectPath = projectCollectionUri + teamProject.Name;
if (workItemChangedEvent.PortfolioProject == teamProjectName)
{
//get the workitem by ID ( CoreFields.IntegerFields[0] == ID ?!)
//check if any of String changed fields
foreach(StringField sf in workItemChangedEvent.ChangedFields.StringFields)
{
//is the State field
if (sf.Name.Equals("State"))
{
//then notify Reuqester
wItem = wiStore.GetWorkItem(workItemChangedEvent.CoreFields.IntegerFields[0].NewValue);
string CollGuid = projectCollection.InstanceId.ToString();
string Requester = wItem.Fields["Requester"].Value.ToString();
string WorkItemId = wItem.Id.ToString();
string mail = GetEmailAddress(Requester);
SendMail(CollGuid, WorkItemId, mail);
}
}
}
}
}
private static string GetEmailAddress(string userDisplayName)
{
DirectorySearcher ds = new DirectorySearcher();
ds.PropertiesToLoad.Add("mail");
ds.Filter = String.Format("(&(displayName={0})(objectCategory=person)((objectClass=user)))", userDisplayName);
SearchResultCollection results = ds.FindAll();
if (results.Count == 0)
{
return string.Empty;
}
ResultPropertyValueCollection values = results[0].Properties["mail"];
if (values.Count == 0)
{
return string.Empty;
}
return values[0].ToString();
}
private static void SendMail(string collID,string workItemId,string tomailAddrees)
{
MailMessage objeto_mail = new MailMessage();
SmtpClient client = new SmtpClient();
client.Port = Port;
client.Host = SMTPHost;
client.Timeout = 200000;
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = true;
client.EnableSsl = true;
client.Credentials = new System.Net.NetworkCredential(MailAddressFrom, Password);
objeto_mail.From = new MailAddress(MailAddressFrom);
objeto_mail.To.Add(new MailAddress(tomailAddrees));
//objeto_mail.CC.Add(new MailAddress("nagarajb#hotmail.com"));
objeto_mail.Subject = "Work Item Changed:"+workItemId;
string mailbody = serverPath+"/tfs/web/wi.aspx?pcguid=" + collID + "&id=" + workItemId;
string mailbody2 = "";
if (ExternalURL.Length > 0)
{
mailbody2 = ExternalURL + "/tfs/web/wi.aspx?pcguid=" + collID + "&id=" + workItemId;
}
string tables = "<table border=1><tr><td>Work Item ID</td><td>" + wItem.Id.ToString() + "</td></tr><tr><td>Title</td><td>" + wItem.Title + "</td></tr><tr><td>State</td><td>" + wItem.State + "</td></tr><tr><td>Assigned To</td><td>" + wItem.Fields["Assigned to"].Value.ToString() + "</td></tr><tr><td>Internal URL</td><td>" + mailbody + "</td></tr><tr><td>External URL</td><td>" + mailbody2 + "</td></tr></table>";
objeto_mail.IsBodyHtml = true;
objeto_mail.Body = "<i>Hi " + wItem.Fields["Requester"].Value.ToString() + ","+"</i></br></br></br>" + tables + " </br></br> Best regards; </br></br>Configuration Management Team</br></br></br>";
client.Send(objeto_mail);
EventLog.WriteEntry("WorkItemChangedEventHandler", "Email Sent");
}
}
}
No errors or exceptions are thrown in the Event Log either. Tracing TFS (trace=true property in web.config) also was of no help.
Maybe someone could help or shed light on this mysterious case?
UPDATE:
Thanks for a reply Giulio Vian!
Here how it goes:
1) I haven't seen the dependencies broken, plus the constructor WorkItemChangedEventHandler is successfully called. That is seen in the Windows Event Log - WorkItemChangedEvent Started message is written.
2) I am not sure how to register the event handler.... i'll look that up
3) I am not sure how this works. I thought just copy-pasting dlls in the appropriate folder will do the trick, and there is no need for an account for the plugin. Mind giving a bit more info on this?
4) yes. Web.config in the main Web config in Application Tier\Web Services
5) Yes. Using an account with Administer rights. if i set a break point in the constructor, it is reached. Any other place in the code is not reached.
Many things can be wrong.
If any dependency is broken, you should see an error in the Event log
If you do not register the "WorkItemChangedEventHandler" event source, no message is written
Can the user account running the plugin access TFS?
How did you enabled the tracing (you pasted a bunch of code, but no configuration)?
Have you attached the debugger to the TFS process and set a breakpoint (do not this on a production TFS)?
We have collected similar suggestions in the documentation for our plugin at
https://github.com/tfsaggregator/tfsaggregator/wiki.
I am writing a C# mapreduce program that runs on the cluster. The system returns an error of
System.Threading.Tasks.Task
The task is cancelled after a timeout of 32 milliseconds. The data is pretty huge .I even changed the data size to check but the error still occurs, so I am assuming it is not due to the data size.
Is there a way to change the task timeout of 32 milliseconds.
I am using Azure, Visual studio 2013 and a cluster with 4 nodes.
The system aggregate exception occured in mscorlib.dll
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Threading;
using Microsoft.Hadoop.MapReduce;
using Microsoft.WindowsAzure.Management.HDInsight;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Hadoop.Client;
namespace StackExtractor
{
//Our Mapper that takes a line of XML input and spits out the {OwnerUserId,ParentId,Score}
//i.e, {User,Question,Weightage}
public class UserQuestionsMapper : MapperBase
{
public override void Map(string inputLine, MapperContext context)
{
try
{
var obj = XElement.Parse(inputLine);
var postType = obj.Attribute("PostTypeId");
if (postType != null && postType.Value == "2")
{
var owner = obj.Attribute("OwnerUserId");
var parent = obj.Attribute("ParentId");
// Write output data. Ignore records will null values if any
if (owner != null && parent != null)
{
context.EmitLine(string.Format("{0},{1}", owner.Value, parent.Value));
}
}
}
catch
{
//Ignore this line if we can't parse
}
}
}
//Our Extraction Job using our Mapper
public class UserQuestionsExtractionJob : HadoopJob<UserQuestionsMapper>
{
public override HadoopJobConfiguration Configure(ExecutorContext context)
{
var config = new HadoopJobConfiguration();
config.DeleteOutputFolder = true;
config.InputPath = "/input/recommender";
config.OutputFolder = "/output/recommender";
return config;
}
}
//Driver that submits this to the cluster in the cloud
//And will wait for the result. This will push your executables to the Azure storage
//and will execute the command line in the head node (HDFS for Hadoop on Azure uses Azure storage)
public class Driver
{
public static void Main()
{
try
{
var azureCluster = new Uri("https://name r.azurehdinsight.net:563");
const string clusterUserName = "****";
const string clusterPassword = "****";
// This is the name of the account under which Hadoop will execute jobs.
// Normally this is just "Hadoop".
const string hadoopUserName = "Hadoop";
// Azure Storage Information.
const string azureStorageAccount = "name.blob.core.windows.net";
const string azureStorageKey = "id;
const string azureStorageContainer = "namecontainer";
const bool createContainerIfNotExist = true;
Console.WriteLine("Connecting : {0} ", DateTime.Now);
var hadoop = Hadoop.Connect(azureCluster,
clusterUserName,
hadoopUserName,
clusterPassword,
azureStorageAccount,
azureStorageKey,
azureStorageContainer,
createContainerIfNotExist);
Console.WriteLine("Starting: {0} ", DateTime.Now);
var result = hadoop.MapReduceJob.ExecuteJob <UserQuestionsExtractionJob>();
var info = result.Info;
Console.WriteLine("Done: {0} ", DateTime.Now);
Console.WriteLine("\nInfo From Server\n----------------------");
Console.WriteLine("StandardError: " + info.StandardError);
Console.WriteLine("\n----------------------");
Console.WriteLine("StandardOut: " + info.StandardOut);
Console.WriteLine("\n----------------------");
Console.WriteLine("ExitCode: " + info.ExitCode);
}
catch (Exception ex)
{
Console.WriteLine("Error: {0} ", ex.StackTrace.ToString(CultureInfo.InvariantCulture));
}
Console.WriteLine("Press Any Key To Exit..");
Console.ReadLine();
}
}
}