I can send notification to specific user from firbase consol but I want to send notification from one device to another specific user device using button click event.Thanks in advance for your support in this regards.
var btnSendNotification = FindViewById<Button>(Resource.Id.btnSendNotification);
btnSendNotification.Click += delegate
{
//this details i will get from EditTextTitle,EditTextMessage,EditTextToken
string title = "title";
string message = "message";
string token = "token";
};
Here adding my current codes for your reference.
MyFirebaseMessagingService class
using System;
using Android.App;
using Android.Content;
using Android.Media;
using Android.Util;
using Firebase.Messaging;
using FCMClient;
using System.Collections.Generic;
using AndroidX.Core.App;
namespace FCMClient
{
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class MyFirebaseMessagingService : FirebaseMessagingService
{
const string TAG = "MyFirebaseMsgService";
public override void OnMessageReceived(RemoteMessage message)
{
Log.Debug(TAG, "From: " + message.From);
// Log.Debug(TAG, "Notification Message Body: " +
message.GetNotification().Body);
var body = message.GetNotification().Body;
Log.Debug(TAG, "Notification Message Body: " + body);
SendNotification(body, message.Data);
}
void SendNotification(string messageBody, IDictionary<string, string> data)
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
foreach (var key in data.Keys)
{
intent.PutExtra(key, data[key]);
}
var pendingIntent = PendingIntent.GetActivity(this,
MainActivity.NOTIFICATION_ID,
intent,
PendingIntentFlags.OneShot);
var notificationBuilder = new NotificationCompat.Builder(this, MainActivity.CHANNEL_ID)
.SetSmallIcon(Resource.Drawable.abc_ic_star_black_36dp)
.SetContentTitle("FCM Message")
.SetContentText(messageBody)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = NotificationManagerCompat.From(this);
notificationManager.Notify(MainActivity.NOTIFICATION_ID,
notificationBuilder.Build());
}
}
}
MyFirebaseIIDService class
using System;
using Android.App;
using Firebase.Iid;
using Android.Util;
namespace FCMClient
{
[Service]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class MyFirebaseIIDService : FirebaseInstanceIdService
{
const string TAG = "MyFirebaseIIDService";
public override void OnTokenRefresh()
{
var refreshedToken = FirebaseInstanceId.Instance.Token;
Log.Debug(TAG, "Refreshed token: " + refreshedToken);
}
}
}
MainActivity
using Android.OS;
using Android.Runtime;
using AndroidX.AppCompat.App;
using Android.Gms.Common;
using Firebase.Messaging;
using Firebase.Iid;
using Android.Util;
using Android.Widget;
namespace FCMClient
{
[Activity(Label = "#string/app_name", Theme = "#style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
static readonly string TAG = "MainActivity";
internal static readonly string CHANNEL_ID = "my_notification_channel";
internal static readonly int NOTIFICATION_ID = 100;
TextView msgText;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
msgText = FindViewById<TextView>(Resource.Id.msgText);
IsPlayServicesAvailable();
CreateNotificationChannel();
var logTokenButton = FindViewById<Button>(Resource.Id.logTokenButton);
logTokenButton.Click += delegate
{
Log.Debug(TAG, "InstanceID token: " + FirebaseInstanceId.Instance.Token);
};
var btnSendNotification = FindViewById<Button>(Resource.Id.btnSendNotification);
btnSendNotification.Click += delegate
{
//this details i will get from EditTextTitle,EditTextMessage,EditTextToken
string title = "title";
string message = "message";
string token = "token";
};
}
public bool IsPlayServicesAvailable()
{
int resultCode =
GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.Success)
{
if (GoogleApiAvailability.Instance.IsUserResolvableError(resultCode))
msgText.Text = GoogleApiAvailability.Instance.GetErrorString(resultCode);
else
{
msgText.Text = "This device is not supported";
Finish();
}
return false;
}
else
{
msgText.Text = "Google Play Services is available.";
return true;
}
}
void CreateNotificationChannel()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var channel = new NotificationChannel(CHANNEL_ID,
"FCM Notifications",
NotificationImportance.Default)
{
Description = "Firebase Cloud Messages appear in this channel"
};
var notificationManager = (NotificationManager)GetSystemService(Android.Content.Context.NotificationService);
notificationManager.CreateNotificationChannel(channel);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
Related
We are working on a Xamarin Forms app that is supposed to upload photos to API in the background. The app is being custom-made for a client by their request, so they will set their phones to whatever permissions need to be set.
Below works fine if the charging cable is plugged in.
I am using BGTaskScheduler (iOS13+) and queuing both types of tasks (BGProcessingTaskRequest and BGAppRefreshTaskRequest) so that if the cable plugged in it would fire off BGProcessingTaskRequest and if not it would wait for BGAppRefreshTaskRequest to get its processing time.
I have added RefreshTaskId and UploadTaskId to Info.plist
AppDelegate.cs in iOS project looks following
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
BGTaskScheduler.Shared.Register(UploadTaskId, null, task => HandleUpload(task as BGProcessingTask));
BGTaskScheduler.Shared.Register(RefreshTaskId, null, task => HandleAppRefresh(task as BGAppRefreshTask));
return base.FinishedLaunching(app, options);
}
public override void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler)
{
Console.WriteLine("HandleEventsForBackgroundUrl");
BackgroundSessionCompletionHandler = completionHandler;
}
public override void OnActivated(UIApplication application)
{
Console.WriteLine("OnActivated");
}
public override void OnResignActivation(UIApplication application)
{
Console.WriteLine("OnResignActivation");
}
private void HandleAppRefresh(BGAppRefreshTask task)
{
HandleUpload(task);
}
public override void DidEnterBackground(UIApplication application)
{
ScheduleUpload();
}
private void HandleUpload(BGTask task)
{
var uploadService = new UploadService();
uploadService.EnqueueUpload();
task.SetTaskCompleted(true);
}
private void ScheduleUpload()
{
var upload = new BGProcessingTaskRequest(UploadTaskId)
{
RequiresNetworkConnectivity = true,
RequiresExternalPower = false
};
BGTaskScheduler.Shared.Submit(upload, out NSError error);
var refresh = new BGAppRefreshTaskRequest(RefreshTaskId);
BGTaskScheduler.Shared.Submit(refresh, out NSError refreshError);
if (error != null)
Console.WriteLine($"Could not schedule BGProcessingTask: {error}");
if (refreshError != null)
Console.WriteLine($"Could not schedule BGAppRefreshTask: {refreshError}");
}
The mechanism that does the upload UploadService is using NSUrlSession, it also writes a temporary file to use CreateUploadTask(request, NSUrl.FromFilename(tempFileName)) that is supposed to work in the background, whole mechanism looks following:
public NSUrlSession uploadSession;
public async void EnqueueUpload()
{
var accountsTask = await App.PCA.GetAccountsAsync();
var authResult = await App.PCA.AcquireTokenSilent(App.Scopes, accountsTask.First())
.ExecuteAsync();
if (uploadSession == null)
uploadSession = InitBackgroundSession(authResult.AccessToken);
var datastore = DependencyService.Get<IDataStore<Upload>>();
var uploads = await datastore.GetUnuploaded();
foreach (var unUploaded in uploads)
{
try
{
string folder = unUploaded.Description;
string subfolder = unUploaded.Category;
if (string.IsNullOrEmpty(folder) || string.IsNullOrEmpty(subfolder))
continue;
var uploadDto = new Dtos.Upload
{
FolderName = folder,
SubFolderName = subfolder,
Image = GetImageAsBase64(unUploaded.ImagePath)
};
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var fileName = Path.GetFileName(unUploaded.ImagePath);
var tempFileName = Path.Combine(documents, $"{fileName}.txt");
string stringContent = await new StringContent(JsonConvert.SerializeObject(uploadDto), Encoding.UTF8, "application/json").ReadAsStringAsync();
await File.WriteAllTextAsync(tempFileName, stringContent);
using (var url = NSUrl.FromString(UploadUrlString))
using (var request = new NSMutableUrlRequest(url)
{
HttpMethod = "POST",
})
{
request.Headers.SetValueForKey(NSObject.FromObject("application/json"), new NSString("Content-type"));
try
{
uploadSession.CreateUploadTask(request, NSUrl.FromFilename(tempFileName));
}
catch (Exception e)
{
Console.WriteLine($"NSMutableUrlRequest failed {e.Message}");
}
}
}
catch (Exception e)
{
if (e.Message.Contains("Could not find a part of the path"))
{
await datastore.DeleteItemAsync(unUploaded.Id);
Console.WriteLine($"deleted");
}
Console.WriteLine($"uploadStore failed {e.Message}");
}
}
}
private string GetImageAsBase64(string path)
{
using (var reader = new StreamReader(path))
using (MemoryStream ms = new MemoryStream())
{
reader.BaseStream.CopyTo(ms);
return Convert.ToBase64String(ms.ToArray());
}
}
public NSUrlSession InitBackgroundSession(string authToken = null, IDataStore<Upload> dataStore = null)
{
Console.WriteLine("InitBackgroundSession");
using (var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration(Identifier))
{
configuration.AllowsCellularAccess = true;
configuration.Discretionary = false;
configuration.AllowsConstrainedNetworkAccess = true;
configuration.AllowsExpensiveNetworkAccess = true;
if (string.IsNullOrWhiteSpace(authToken) == false)
{
configuration.HttpAdditionalHeaders = NSDictionary.FromObjectsAndKeys(new string[] { $"Bearer {authToken}" }, new string[] { "Authorization" });
}
return NSUrlSession.FromConfiguration(configuration, new UploadDelegate(dataStore), null);
}
}
}
public class UploadDelegate : NSUrlSessionTaskDelegate, INSUrlSessionDelegate
{
public IDataStore<Upload> Datastore { get; }
public UploadDelegate(IDataStore<Upload> datastore)
{
this.Datastore = datastore;
}
public override void DidCompleteWithError(NSUrlSession session, NSUrlSessionTask task, NSError error)
{
Console.WriteLine(string.Format("DidCompleteWithError TaskId: {0}{1}", task.TaskIdentifier, (error == null ? "" : " Error: " + error.Description)));
if (error == null)
{
ProcessCompletedTask(task);
}
}
public void ProcessCompletedTask(NSUrlSessionTask sessionTask)
{
try
{
Console.WriteLine(string.Format("Task ID: {0}, State: {1}, Response: {2}", sessionTask.TaskIdentifier, sessionTask.State, sessionTask.Response));
if (sessionTask.Response == null || sessionTask.Response.ToString() == "")
{
Console.WriteLine("ProcessCompletedTask no response...");
}
else
{
var resp = (NSHttpUrlResponse)sessionTask.Response;
Console.WriteLine("ProcessCompletedTask got response...");
if (sessionTask.State == NSUrlSessionTaskState.Completed && resp.StatusCode == 201)
{
Console.WriteLine("201");
}
}
}
catch (Exception ex)
{
Console.WriteLine("ProcessCompletedTask Ex: {0}", ex.Message);
}
}
public override void DidBecomeInvalid(NSUrlSession session, NSError error)
{
Console.WriteLine("DidBecomeInvalid" + (error == null ? "undefined" : error.Description));
}
public override void DidFinishEventsForBackgroundSession(NSUrlSession session)
{
Console.WriteLine("DidFinishEventsForBackgroundSession");
}
public override void DidSendBodyData(NSUrlSession session, NSUrlSessionTask task, long bytesSent, long totalBytesSent, long totalBytesExpectedToSend)
{
}
}
Everything works if the iOS charger cable is plugged in, however, if it isn't nothing fires. I have a network debugging set up with plenty of logging into the console, and I can see that nothing happens on iPhone.
"Low power mode" setting on iOS is off.
I have watched Background execution demystified and I am setting session configuration.Discretionary = false;
How do I make the NSUrlSession upload task to fire when iOS charger cable is unplugged on iOS 14.4?
Following works without charging cable:
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public Action BackgroundSessionCompletionHandler { get; set; }
public static string UploadTaskId { get; } = "XXX.upload";
public static NSString UploadSuccessNotificationName { get; } = new NSString($"{UploadTaskId}.success");
public static string RefreshTaskId { get; } = "XXX.refresh";
public static NSString RefreshSuccessNotificationName { get; } = new NSString($"{RefreshTaskId}.success");
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
BGTaskScheduler.Shared.Register(UploadTaskId, null, task => HandleUpload(task as BGProcessingTask));
BGTaskScheduler.Shared.Register(RefreshTaskId, null, task => HandleAppRefresh(task as BGAppRefreshTask));
return base.FinishedLaunching(app, options);
}
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
return true;
}
public override void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler)
{
Console.WriteLine("HandleEventsForBackgroundUrl");
BackgroundSessionCompletionHandler = completionHandler;
}
public override void OnActivated(UIApplication application)
{
Console.WriteLine("OnActivated");
var uploadService = new UploadService();
uploadService.EnqueueUpload();
}
public override void OnResignActivation(UIApplication application)
{
Console.WriteLine("OnResignActivation");
}
private void HandleAppRefresh(BGAppRefreshTask task)
{
task.ExpirationHandler = () =>
{
Console.WriteLine("BGAppRefreshTask ExpirationHandler");
var refresh = new BGAppRefreshTaskRequest(RefreshTaskId);
BGTaskScheduler.Shared.Submit(refresh, out NSError refreshError);
if (refreshError != null)
Console.WriteLine($"BGAppRefreshTask ExpirationHandler Could not schedule BGAppRefreshTask: {refreshError}");
};
HandleUpload(task);
}
public override void DidEnterBackground(UIApplication application) => ScheduleUpload();
private void HandleUpload(BGTask task)
{
Console.WriteLine("HandleUpload");
var uploadService = new UploadService();
uploadService.EnqueueUpload();
task.SetTaskCompleted(true);
}
private void ScheduleUpload()
{
Console.WriteLine("ScheduleUpload");
var upload = new BGProcessingTaskRequest(UploadTaskId)
{
RequiresNetworkConnectivity = true,
RequiresExternalPower = false
};
BGTaskScheduler.Shared.Submit(upload, out NSError error);
var refresh = new BGAppRefreshTaskRequest(RefreshTaskId);
BGTaskScheduler.Shared.Submit(refresh, out NSError refreshError);
if (error != null)
Console.WriteLine($"Could not schedule BGProcessingTask: {error}");
if (refreshError != null)
Console.WriteLine($"Could not schedule BGAppRefreshTask: {refreshError}");
}
}
then Upload service:
public class UploadService : IUploadService
{
private const string uploadUrlString = "https://Yadyyadyyada";
public async void EnqueueUpload()
{
var accountsTask = await App.PCA.GetAccountsAsync();
var authResult = await App.PCA.AcquireTokenSilent(App.Scopes, accountsTask.First())
.ExecuteAsync();
try
{
var uploadDto = new object();
var message = new HttpRequestMessage(HttpMethod.Post, uploadUrlString);
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
message.Content = new StringContent(JsonConvert.SerializeObject(uploadDto), Encoding.UTF8, "application/json");
var response = await httpClient.SendAsync(message);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
}
}
catch (Exception e)
{
Console.WriteLine($"EnqueueUpload {e.Message}");
}
}
}
I am using Asp.Net signal for sending user specific notification. Everything working fine in debug mode using visual studio but the same breaks while deployed to Azure.
I am using redis cache.
Startup.cs
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(NotifSystem.Web.Startup))]
namespace NotifSystem.Web
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
GlobalHost.DependencyResolver.UseStackExchangeRedis(new RedisScaleoutConfiguration("mySrver:6380,password=password,ssl=True", "YourServer"));
app.MapSignalR();
}
}
}
My Hub Class:
using Microsoft.AspNet.SignalR;
using NotificationHub.Models.Hubs;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NotificationHub.Hubs
{
public class NotificationHub : Hub
{
private static readonly ConcurrentDictionary<string, UserHubModels> Users =
new ConcurrentDictionary<string, UserHubModels>(StringComparer.InvariantCultureIgnoreCase);
//private NotifEntities context = new NotifEntities();
//Logged Use Call
public void GetNotification()
{
try
{
string loggedUser = Context.User.Identity.Name;
//Get TotalNotification
//string totalNotif = LoadNotifData(loggedUser);
//Send To
UserHubModels receiver;
if (Users.TryGetValue(loggedUser, out receiver))
{
var cid = receiver.ConnectionIds.FirstOrDefault();
var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
context.Clients.Client(cid).broadcaastNotif();
}
}
catch (Exception ex)
{
ex.ToString();
}
}
//Specific User Call
public void SendNotification(string SentTo,string Notification)
{
try
{
//Get TotalNotification
//string totalNotif = LoadNotifData(SentTo);
//Send To
UserHubModels receiver;
if (Users.TryGetValue(SentTo, out receiver))
{
var cid = receiver.ConnectionIds.FirstOrDefault();
var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
context.Clients.Client(cid).broadcaastNotif(Notification);
}
}
catch (Exception ex)
{
ex.ToString();
}
}
private string LoadNotifData(string userId)
{
return userId;
int total = 0;
//var query = (from t in context.Notifications
// where t.SentTo == userId
// select t)
// .ToList();
total = 6;
return total.ToString();
}
public override Task OnConnected()
{
string userName = Context.User.Identity.Name;
string connectionId = Context.ConnectionId;
var user = Users.GetOrAdd(userName, _ => new UserHubModels
{
UserName = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds)
{
user.ConnectionIds.Add(connectionId);
if (user.ConnectionIds.Count == 1)
{
Clients.Others.userConnected(userName);
}
}
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string userName = Context.User.Identity.Name;
string connectionId = Context.ConnectionId;
UserHubModels user;
Users.TryGetValue(userName, out user);
if (user != null)
{
lock (user.ConnectionIds)
{
user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
if (!user.ConnectionIds.Any())
{
UserHubModels removedUser;
Users.TryRemove(userName, out removedUser);
Clients.Others.userDisconnected(userName);
}
}
}
return base.OnDisconnected(stopCalled);
}
}
}
Javascript Code:
var hub = $.connection.notificationHub;
hub.client.broadcaastNotif = function (notification) {
setTotalNotification(notification)
};
$.connection.hub.start()
.done(function () {
console.log("Connected!");
hub.server.getNotification();
})
.fail(function () {
console.log("Could not Connect!");
});
});
function setTotalNotification(notification) {
if (notification) {
GetUnreadNotificationCount();
$('#m_topbar_notification_icon .m-nav__link-icon').addClass('m-animate-shake');
$('#m_topbar_notification_icon .m-nav__link-badge').addClass('m-animate-blink');
}
else {
$('#m_topbar_notification_icon .m-nav__link-icon').removeClass('m-animate-shake');
$('#m_topbar_notification_icon .m-nav__link-badge').removeClass('m-animate-blink');
}
}
I have enabled Websocket for that particular App Service.
Cross user notification sending is not successful it only works if the logged in user sends notification to himself only.
Update:
I checked that while a logged in user is doing an activity so that the notification goes to that particular user then it works. Like if a user user1 sends a notification to user1 then there is no issue.
We had same problem with our Azure SignalR redis BackPlane POC.
But We tried redis with No SSL port then the Azure SignalR redis BackPlane started working fine. Please check the screenshot Below. Now since the enviorment is self contained we do not need it even HTTPS. We are managing it by Resource Groups and Port Whitelisting.
I have followed all related threads for deadlocking the AcquireTokenAsync operation in ASP.NET MVC. But still I am facing the timeout issue - sometimes after 1 day, sometimes after 3 days. When I restart my web app all works fine again.
Here is my Token Bearer Class which retrieves the token:
public static class SSASTokenBearer
{
public static string Token = string.Empty;
public static DateTime TokenExpiryTime = DateTime.MinValue;
static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
public static bool isTokenGenerated = false;
public static int _counter = 0;
public static bool IsThirdAttempt { get; set; }
public static List<string> lstToken = new List<string>();
public async static Task<string> GetAppOnlyAccessToken(string domain, string resourceUrl, string clientId, string clientSecret, string authUrl)
{
if (TokenExpiryTime > DateTime.UtcNow)
{
//if (_counter.Equals(Convert.ToInt32(Attempt.First)))
//{
// isTokenGenerated = false;
//}
return Token;
}
else
{
await semaphoreSlim.WaitAsync();
//ClearTokenListAndAttemptCounter();
try
{
if (TokenExpiryTime < DateTime.UtcNow)
{
_counter++;
var authority = $"{authUrl}/{domain}/oauth2/token";
var authContext = new AuthenticationContext(authority);
// Config for OAuth client credentials
var clientCred = new ClientCredential(clientId, clientSecret);
try
{
AuthenticationResult authenticationResult = await authContext.AcquireTokenAsync(resourceUrl, clientCred).ConfigureAwait(false);
//get access token
TokenExpiryTime = authenticationResult.ExpiresOn.DateTime;
Token = authenticationResult.AccessToken;
//lstToken.Add(Token);
//isTokenGenerated = true;
}
catch (AdalException ex)
{
throw ex;
}
}
}
finally
{
semaphoreSlim.Release();
}
}
return Token;
}
}
Here is the actual calling of the Bearer Token Class in the Open() method
using Microsoft.AnalysisServices.AdomdClient;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BMIS2.Data.Repositories.PreventativeMaintenance.Dax
{
public enum Attempt
{
First = 1,
Second = 2
}
public abstract class AbstactDal
{
public readonly string BMIS2DataBaseAzureSSAS = ConfigurationManager.AppSettings["BMIS2DataBaseAzureSSAS"];
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
//change the connection password and url dynamically and use initial catalog from web.config
private static string AzureSSASClientId;
private static string AzureSSASClientSecret;
private static string AzureSSASDomain;
private static string AzureSSASURL = Helper.AzureSSASURL;
private static string AzureAuthUrl;
protected AdomdConnection DaxConnection = null;
public AdomdCommand DaxCommand = null;
private static readonly object padlock = new object();
//private static Task<string> tskToken = null;
private bool switchConnection = Convert.ToBoolean(ConfigurationManager.AppSettings["SwitchConnection"]);
private static string ConnectionStr = ConfigurationManager.AppSettings["BMIS2DataBaseAzureSSASStatic"];
//public async Task Execute(string query, Func<T> MethodName)
public async Task ExecuteQuery(string query, Action MethodName)
{
if (switchConnection)
{
await Open(ConnectionStr);
}
else
{
await Open($"Provider=MSOLAP;Data Source={AzureSSASURL};Initial Catalog={Helper.SSASDB};User ID=;Password={await Init()};Persist Security Info=True;Impersonation Level=Impersonate");
}
await ExecuteDaxReader(query, MethodName);
Close();
}
private async Task<string> Init()
{
AzureSSASClientId = Helper.AzureSSASClientId;
AzureSSASClientSecret = Helper.AzureSSASClientSecret;
AzureSSASDomain = Helper.AzureSSASDomain;
AzureAuthUrl = Helper.AzureAuthUrl;
var token= await SSASTokenBearer.GetAppOnlyAccessToken(AzureSSASDomain, $"https://{Helper.AzureSSASZone}", AzureSSASClientId, AzureSSASClientSecret, AzureAuthUrl);
return token;
}
private async Task Open(string BMIS2DataBaseAzureSSAS)
{
DaxConnection = new AdomdConnection(BMIS2DataBaseAzureSSAS);
try
{
DaxConnection.Open();
}
catch (Exception ex)
{
Log.Warn(ex.Message.ToString());
await Open($"Provider=MSOLAP;Data Source={AzureSSASURL};Initial Catalog={Helper.SSASDB};User ID=;Password={ await Init()};Persist Security Info=True;Impersonation Level=Impersonate");
}
DaxCommand = new AdomdCommand();
DaxCommand.Connection = DaxConnection;
}
private void Close()
{
DaxConnection.Close();
}
public abstract Task ExecuteDaxReader(string query, Action MethodName);
}
}
In the implementation repository, each repository has its own common method to execute and read data from the data reader. We retrieve the DAX query from sql db and hit the same query to the SSAS Server.
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using BMIS2.Common.CacheProviders;
using BMIS2.Data.Repositories.PreventativeMaintenance.Dax;
using BMIS2.Entity.ProcessPerformance;
using Microsoft.AnalysisServices.AdomdClient;
namespace BMIS2.Data.Repositories.PreventativeMaintenance.Imp
{
public class DayOfTheWeekRepository : AbstactDal, IDayOfTheWeekRepository
{
public readonly string BMIS2DataBase = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public readonly int DefaultQuerySessionTime = Convert.ToInt32(ConfigurationManager.AppSettings["DefaultQuerySessionTime"]);
private readonly ICacheProvider _cacheProvider;
private List<AbstractProcessPerformanceDayOfTheWeek> lstRoCont = null;
private bool IsROCount=false;
public DayOfTheWeekRepository(ICacheProvider cacheProvider)
{
_cacheProvider = cacheProvider;
}
public void GetIsRoCount()
{
try
{
using (var reader = DaxCommand.ExecuteReader())
{
while (reader.Read())
{
IsROCount = ((reader["FACT_ROSale[RoCount]"] == null || reader["FACT_ROSale[RoCount]"].ToString() == "") ? 0 : Convert.ToInt32(reader["FACT_ROSale[RoCount]"])) > 0 ? true : false;
}
}
}
catch(Exception ex)
{
Log.Error(ex.Message.ToString());
throw ex;
}
}
public static bool HasValue( double value)
{
return !Double.IsNaN(value) && !Double.IsInfinity(value);
}
public void GetResultForRoCount()
{
try
{
lstRoCont = new List<AbstractProcessPerformanceDayOfTheWeek>();
if (IsROCount)
{
using (var reader = DaxCommand.ExecuteReader())
{
while (reader.Read())
{
lstRoCont.Add(new ROCount()
{
DayOfTheWeek = (reader["[Day of Week]"] == null || reader["[Day of Week]"].ToString() == "") ? "" : Convert.ToString(reader["[Day of Week]"]),
TestCount = (reader["[Test Count]"] == null || reader["[Test Count]"].ToString() == "") ? 0 : Convert.ToInt32(reader["[Test Count]"]),
RoCount = (reader["[RO Count]"] == null || reader["[RO Count]"].ToString() == "") ? 0 : Convert.ToInt32(reader["[RO Count]"]),
RoTestedPercent = Math.Round((reader["[RO Tested %]"] == null || reader["[RO Tested %]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[RO Tested %]"]),1)
//RoTestedPercent = HasValue(Math.Round((((reader["[Test Count]"] == null || reader["[Test Count]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[Test Count]"])) / ((reader["[RO Count]"] == null || reader["[RO Count]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[RO Count]"]))) * 100, 1)) ? Math.Round(((reader["[Test Count]"] == null || reader["[Test Count]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[Test Count]"])) / ((reader["[RO Count]"] == null || reader["[RO Count]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[RO Count]"])) * 100, 1) : 0,
});
}
}
}
else
{
using (var reader = DaxCommand.ExecuteReader())
{
while (reader.Read())
{
lstRoCont.Add(new NoROCount()
{
DayOfTheWeek = (reader["[Day of Week]"] == null || reader["[Day of Week]"].ToString() == "") ? "" : Convert.ToString(reader["[Day of Week]"]),
//TotalCountPercent = HasValue(totalSum)? Math.Round((totalSum * 100),1) : 0,
TotalCountPercent = Math.Round((reader["[Test Count %]"] == null || reader["[Test Count %]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[Test Count %]"]), 1),
TestCount = (reader["[Test Count]"] == null || reader["[Test Count]"].ToString() == "") ? 0 : Convert.ToInt32(reader["[Test Count]"])
});
}
}
}
}
catch (Exception ex)
{
Log.Error(ex.Message.ToString());
throw ex;
}
}
public async Task<List<AbstractProcessPerformanceDayOfTheWeek>> GetDayOfTheWeekData(DayOfWeekFiltersObject filterSearch,bool IsRo)
{
IsROCount = IsRo;
string RowCountQuery = string.Empty;
// Stopwatch sw = new Stopwatch();
//sw.Start();
try {
using (var con = Database.GetConnection(BMIS2DataBase))
{
con.Open();
using (var command = new SqlCommand())
{
SqlCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = IsRo? "[BMIS].[R_SP_GetRoCountQuery]" : "[BMIS].[R_SP_GetNoRoCountQuery]";
cmd.Parameters.Add(new SqlParameter("#clientid", filterSearch.ClientId));
cmd.Parameters.Add(new SqlParameter("#StartYear", DateTime.Parse(filterSearch.StartDate).ToString("yyyyMMdd")));
cmd.Parameters.Add(new SqlParameter("#EndYear", DateTime.Parse(filterSearch.EndDate).ToString("yyyyMMdd")));
cmd.Parameters.Add(new SqlParameter("#LocationId", filterSearch.SelectedLocationId));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#ToolType", filterSearch.ToolTypeName));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#TestType", filterSearch.TestType));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#VehicleStatus", filterSearch.VehicleStatusName==null?null:String.Join(",", filterSearch.VehicleStatusName)));
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
RowCountQuery = reader.GetString(reader.GetOrdinal("query"));
}
}
}
}
}
catch(Exception ex)
{
Log.Error(ex.Message);
}
//sw.Stop();
await this.ExecuteQuery(RowCountQuery, GetResultForRoCount);
return lstRoCont;
}
public async Task<bool> IsRowCount(DayOfWeekFiltersObject filterObj)
{
//HttpContext.Current.Session["ClientIdRoCount"] = ClientId;
string RowCountQuery = string.Empty;
using (var con = Database.GetConnection(BMIS2DataBase))
{
con.Open();
using (var command = new SqlCommand())
{
SqlCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[BMIS].[R_SP_IsRoCountQuery]";
cmd.Parameters.Add(new SqlParameter("#clientid", filterObj.ClientId));
cmd.Parameters.Add(new SqlParameter("#StartYear", DateTime.Parse(filterObj.StartDate).ToString("yyyyMMdd")));
cmd.Parameters.Add(new SqlParameter("#EndYear", DateTime.Parse(filterObj.EndDate).ToString("yyyyMMdd")));
cmd.Parameters.Add(new SqlParameter("#LocationId", filterObj.SelectedLocationId));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#ToolType", filterObj.ToolTypeName));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#TestType", filterObj.TestType));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#VehicleStatus", filterObj.VehicleStatusName==null?null:String.Join(",",filterObj.VehicleStatusName)));
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
RowCountQuery = reader.GetString(reader.GetOrdinal("query"));
}
}
}
}
await this.ExecuteQuery(RowCountQuery, GetIsRoCount);
return IsROCount;
}
public override async Task ExecuteDaxReader(string query, Action MethodName)
{
DaxCommand.CommandText = query;
MethodName();
}
}
}
This is how there are 20 repositories that are implementing the same Abstract Dal.
I would be extremely thankful if anyone can help me resolve this issue.
Try testing your code by setting your TokenCache to null in the AuthenticationContext constructor and removing the ConfigureAwait(false). Without ConfigureAwait(false) it should deadlock immediately and with ConfigureAwait(false) it should work every time. Have you ensured that you are hitting that section?
Apparently this is an issue with later versions. One workaround is to downgrade to version 2.19.208020213
https://github.com/Azure/azure-sdk-for-net/issues/1432
I have gone through each Repository 1 by 1 and founded in few cases my colleagues has not put await to async functions which was causing deadlock. I have put this under monitoring and yes we have put ConfigureAwait(false).
I really appreciate your comment. Thanks
I have created a Serilog sink that talks to a web service which persists log messages to a database. It works most of the time. Even in cases where there is an exception calling the web service it will log to the Selflog file. However, However, periodically it simply stops logging either to our custom web service or to the self log file and after it stops logging it never begins logging anywhere else. This is being executed in a windows service and I have to stop and restart the windows service before it begins logging again. A typical exception that i might get from the web service call is: "Task was cancelled". This would be caused because the webservice does not respond within the configured timeout period. As I said normally it will properly write the events to the selflog file. Only periodically will it just stop logging everywhere. I should also say that the volume of logs being generated is very high.
This is a dnx project and here is a portion of the project.json file:
"dependencies": {
"Newtonsoft.Json": "8.0.2",
"Serilog.Sinks.PeriodicBatching": "2.0.0",
"Serilog": "2.0.0-beta-465",
"Serilog.Extensions.Logging": "1.0.0-rc1-final-10092",
"JA.AppCentral.Logging.Contracts": "0.1.0-alpha1-*",
"FluentAssertions": "4.2.2",
"Microsoft.Extensions.Configuration": "1.0.0-rc1-final",
"Microsoft.AspNet.WebApi.Client": "4.0.30506"
}
I have included relevant portions of the sink class. It inherits from periodic batching sink.
The code used to configure the Selflog follows:
FileStream fs = new FileStream(selfLogFilePath, fileMode, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
Serilog.Debugging.SelfLog.Out = TextWriter.Synchronized(sw);
Here is the sink code:enter code here
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serilog;
using System.Net.Http;
using Serilog.Core;
using Serilog.Events;
using System.Net.Http.Headers;
using System.IO;
using Serilog.Formatting.Json;
using Serilog.Debugging;
using Newtonsoft.Json;
using JA.AppCentral.Logging.Contracts;
using Microsoft.Extensions.Configuration;
using Serilog.Sinks.PeriodicBatching;
using System.Diagnostics;
using System.Threading;
namespace JA.AppCentral.Logging
{
public class AppCentralSink: Serilog.Sinks.PeriodicBatching.PeriodicBatchingSink
{
readonly HttpClient _httpClient;
LoggingLevelSwitch _levelControlSwitch;
const string BulkAddUri = "api/appLogging/bulkAdd";
private Uri _baseUri;
private string _baseUriPath;
readonly long? _eventBodyLimitBytes = 1000000;
static readonly TimeSpan RequiredLevelCheckInterval = TimeSpan.FromSeconds(10);
private TimeSpan _timeout = TimeSpan.FromMinutes(1);
private bool _saveMessageTemplate;
private int eventsCount;
private LoggingRepositoryServiceResponse _logServiceResponse;
private int _totalInsertedRecords;
public event EventHandler<ResponseEventArgs> ResponseReceived = delegate { };
DateTime _nextRequiredLevelCheckUtc = DateTime.Now.Add(RequiredLevelCheckInterval);
private int osId;
private string server;
private string username;
private int threadId;
private string appcode;
/// <summary>
/// Overloaded constructor, to pass AppSettings via IConfiguration , instead of separate parameters
/// </summary>
/// <param name="config"></param>
public AppCentralSink(IConfiguration config)
: base(Convert.ToInt32(GetConfigParams(config)["BatchSizeLimit"]),
TimeSpan.FromSeconds(Convert.ToDouble(GetConfigParams(config)["BatchEmitIntervalSeconds"])))
{
Dictionary<string, string> appSettingDict = GetConfigParams(config);
long tempLongVal;
long? eventBodyLimitBytes = long.TryParse(appSettingDict["EventBodyMaxSizeBytes"], out tempLongVal) ? tempLongVal : (long?)null;
if (eventBodyLimitBytes != null)
_eventBodyLimitBytes = eventBodyLimitBytes;
bool saveMessageTemplate = Convert.ToBoolean(appSettingDict["LogMessageTemplate"]);
if (saveMessageTemplate != false)
_saveMessageTemplate = saveMessageTemplate;
string serverUrl = appSettingDict["Url"];
//baseUri = "http://localhost:49774/";
if (!serverUrl.EndsWith("/"))
serverUrl += "/";
_baseUriPath = serverUrl;
_baseUri = new Uri(serverUrl);
TimeSpan timeout = TimeSpan.FromSeconds(Convert.ToDouble(appSettingDict["WebRequestTimeoutSeconds"]));
if (timeout != default(TimeSpan))
_timeout = timeout;
//Default Authentication via http client handler
HttpClientHandler handler = new HttpClientHandler()
{
PreAuthenticate = true,
UseDefaultCredentials = true
};
_httpClient = new HttpClient(handler);
_httpClient.BaseAddress = _baseUri;
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.Timeout = _timeout;
//Init Context properties
OsId = Process.GetCurrentProcess().Id;
Server = Environment.MachineName;
Username = Environment.UserName;
ThreadId = Thread.CurrentThread.ManagedThreadId;
Appcode = config["JA:AppCatalog:AppCode"];
}
private static Dictionary<string,string> GetConfigParams(IConfiguration config)
{
Dictionary<string, string> appSettings = new Dictionary<string, string>();
var SerilogSection = config.GetSection("AppCentralLogging");
appSettings.Add("Url", SerilogSection["Url"]);
appSettings.Add("BatchSizeLimit", SerilogSection["BatchSizeLimit"]);
appSettings.Add("BatchEmitIntervalSeconds", SerilogSection["BatchEmitIntervalSeconds"]);
appSettings.Add("EventBodyMaxSizeBytes", SerilogSection["EventBodyMaxSizeBytes"]);
appSettings.Add("LogMessageTemplate", SerilogSection["LogMessageTemplate"]);
appSettings.Add("WebRequestTimeoutSeconds", SerilogSection["WebRequestTimeoutSeconds"]);
appSettings.Add("SelfLogFileLocationAndPrefix", config["Serilog:SelfLogFileLocationAndPrefix"]);
return appSettings;
}
// The sink must emit at least one event on startup, and the server be
// configured to set a specific level, before background level checks will be performed.
protected override void OnEmptyBatch()
{
if (_levelControlSwitch != null &&
_nextRequiredLevelCheckUtc < DateTime.Now)
{
EmitBatch(Enumerable.Empty<LogEvent>());
}
}
protected override async Task EmitBatchAsync(IEnumerable<LogEvent> events)
{
_nextRequiredLevelCheckUtc = DateTime.Now.Add(RequiredLevelCheckInterval);
var formatter = new JsonFormatter();
List<LogEntry> logEntriesList = new List<LogEntry>();
try
{
foreach (var logEvent in events)
{
EventsCount++;
LogEntry jaLogEvent = ConvertToLogEntry(logEvent);
if (_eventBodyLimitBytes.HasValue)
{
var scratch = new StringWriter();
formatter.Format(logEvent, scratch);
var buffered = scratch.ToString();
if (Encoding.UTF8.GetByteCount(buffered) > _eventBodyLimitBytes.Value)
{
SelfLog.WriteLine("Event JSON representation exceeds the byte size limit of {0} set for this sink and will be dropped; data: {1}", _eventBodyLimitBytes, buffered);
}
else
{
logEntriesList.Add(jaLogEvent);
}
}
else
{
logEntriesList.Add(jaLogEvent);
}
}
var result = await _httpClient.PostAsJsonAsync(BulkAddUri, logEntriesList);
if (!result.IsSuccessStatusCode)
{
try
{
var error = await result.Content.ReadAsStringAsync();
var responseExcep = new Exception(error);
throw responseExcep;
}
catch (Exception e)
{
SelfLog.WriteLine("FailedEvents: " + GetEventsAsString(events));
throw new Exception("Error calling Logging Web Service: status code: " + result.StatusCode +
" reason: " + result.ReasonPhrase + " excep: " + e.ToString());
}
}
_logServiceResponse = await result.Content.ReadAsAsync<LoggingRepositoryServiceResponse>();
if (_logServiceResponse.ResponseException != null)
{
SelfLog.WriteLine("FailedEvents: " + GetEventsAsString(events));
SelfLog.WriteLine(_logServiceResponse.ResponseMessage);
throw new Exception("Error calling Logging Web Service: " +
_logServiceResponse.ResponseMessage);
}
_totalInsertedRecords = _totalInsertedRecords + _logServiceResponse.NumRecordsInserted;
ResponseReceived(this, new ResponseEventArgs(result));
}
catch (Exception e)
{
SelfLog.WriteLine("Error processing log batch, excep: " + e.ToString());
SelfLogEvents(events);
throw;
}
}
private void SelfLogEvents(IEnumerable<LogEvent> events)
{
SelfLog.WriteLine("Failed to write following log events:");
foreach (var e in events)
{
SelfLog.WriteLine($" Event: " + e.RenderMessage());
}
}
private string GetEventsAsString(IEnumerable<LogEvent> events)
{
string eventsResult = string.Empty;
foreach(LogEvent le in events)
{
eventsResult += "[" + le.RenderMessage() + "]";
}
return eventsResult;
}
private LogEntry ConvertToLogEntry(LogEvent logEvent)
{
string propertiesString = JsonConvert.SerializeObject(logEvent.Properties);
string messageTemplate = _saveMessageTemplate == true ? logEvent.MessageTemplate.Text : string.Empty;
//Append Exception to the message if it's not null
string logEventMessage = logEvent.RenderMessage();
if (logEvent.Exception != null)
{
logEventMessage = logEventMessage + " Exception: " + logEvent.Exception.ToString();
}
LogEntry logEntry = new LogEntry("AppCode", "Action", logEvent.Level.ToString(), messageTemplate,
logEventMessage, propertiesString,
logEvent.Timestamp);
//Append additional properties
if (String.IsNullOrEmpty(Appcode))
{
logEntry.AppCode = logEvent.Properties.Keys.Contains("appcode") ? logEvent.Properties["appcode"].ToString().Replace("\"", "") : string.Empty;
logEntry.OsPId = logEvent.Properties.Keys.Contains("os_pid") ? logEvent.Properties["os_pid"].ToString().Replace("\"", "") : string.Empty;
logEntry.ThreadId = logEvent.Properties.Keys.Contains("thread_id") ? logEvent.Properties["thread_id"].ToString().Replace("\"", "") : string.Empty;
logEntry.Server = logEvent.Properties.Keys.Contains("server") ? logEvent.Properties["server"].ToString().Replace("\"", "") : string.Empty;
logEntry.Username = logEvent.Properties.Keys.Contains("username") ? logEvent.Properties["username"].ToString().Replace("\"", "") : string.Empty;
}
else
{
logEntry.AppCode = Appcode;
logEntry.OsPId = OsId.ToString();
logEntry.ThreadId = ThreadId.ToString();
logEntry.Server = Server;
logEntry.Username = Username;
}
logEntry.SessionId = logEvent.Properties.Keys.Contains("session_id") ? logEvent.Properties["session_id"].ToString().Replace("\"", "") : string.Empty;
logEntry.Action = logEvent.Properties.Keys.Contains("action") ? logEvent.Properties["action"].ToString().Replace("\"", "") : string.Empty;
//Append SourceContext
//Append SourceContext
LogEventPropertyValue propertyValue;
if (logEvent.Properties.TryGetValue("SourceContext", out propertyValue))
{
logEntry.SourceContext = propertyValue.ToString().Trim(new[] { '"' });
}
return logEntry;
}
public int EventsCount
{
get
{
return eventsCount;
}
set
{
eventsCount = value;
}
}
public LoggingRepositoryServiceResponse LogServiceResponse
{
get
{
return _logServiceResponse;
}
set
{
_logServiceResponse = value;
}
}
public int TotalInsertedRecords
{
get
{
return _totalInsertedRecords;
}
set
{
_totalInsertedRecords = value;
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
_httpClient.Dispose();
}
public HttpClient HttpClient
{
get { return _httpClient; }
}
}
public class ResponseEventArgs : EventArgs
{
public ResponseEventArgs(HttpResponseMessage response)
{
Response = response;
}
public HttpResponseMessage Response { get; }
}
}
I would like to know can I create a new linked task when I create a workitem.
Can anyone give a tip of how to do this?
I've gone through some old code that I previously used for this scenario. The following code creates a linked task whenever a new bug is set to approved.
The code filters to a specific Team Project and uses a specific account to connect. You need to enter these before the plugin will work. You can then modify this code to create the tasks you want.
For a general introduction to server plugins and how to turn the code below into a functioning plugin see Extending Team Foundation
using Microsoft.TeamFoundation.Framework.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.WorkItemTracking.Server;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Client;
using System.Net;
using System.Collections;
namespace TfsExtension.CreateTaskForBug
{
public class CreateTaskForBugEventHandler : ISubscriber
{
const string projectName = "<Enter your project name here>";
public string Name
{
get
{
return "CreateTaskForBugEventHandler";
}
}
public SubscriberPriority Priority
{
get
{
return SubscriberPriority.Normal;
}
}
public EventNotificationStatus ProcessEvent(
TeamFoundationRequestContext requestContext,
NotificationType notificationType,
object notificationEventArgs,
out int statusCode,
out string statusMessage,
out ExceptionPropertyCollection properties)
{
statusCode = 0;
properties = null;
statusMessage = String.Empty;
try
{
ProcessNotification(notificationType, notificationEventArgs, requestContext);
}
catch (Exception exception)
{
TeamFoundationApplicationCore.LogException("Error processing event", exception);
}
return EventNotificationStatus.ActionPermitted;
}
private static void ProcessNotification(NotificationType notificationType, object notificationEventArgs, TeamFoundationRequestContext requestContext)
{
if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent)
{
var ev = notificationEventArgs as WorkItemChangedEvent;
if (ev.PortfolioProject == projectName)
{
string workItemType = (from field in ev.CoreFields.StringFields
where field.Name == "Work Item Type"
select field.NewValue).Single();
if (workItemType == "Bug")
{
ProcessBug(ev, requestContext);
}
}
}
}
private static void ProcessBug(WorkItemChangedEvent ev, TeamFoundationRequestContext requestContext)
{
var stateChange = (from field in ev.ChangedFields.StringFields
where field.Name == "State" && field.NewValue == "Approved"
select field).SingleOrDefault();
if (stateChange != null)
{
AddChildTaskToBug(ev, requestContext);
}
}
private static void AddChildTaskToBug(WorkItemChangedEvent ev, TeamFoundationRequestContext requestContext)
{
WorkItemStore wiStore = GetWorkItemStore(requestContext);
WorkItem witem = wiStore.GetWorkItem(ev.CoreFields.IntegerFields[0].NewValue);
Project teamProject = witem.Project;
int bugID = witem.Id;
string bugTitle = witem.Fields["System.Title"].Value.ToString();
string bugAssignedTo = witem.Fields["System.AssignedTo"].Value.ToString();
string bugAreaPath = witem.Fields["System.AreaPath"].Value.ToString();
string bugIterationPath = witem.Fields["System.IterationPath"].Value.ToString();
string bugChangedBy = witem.Fields["System.ChangedBy"].OriginalValue.ToString();
string bugTeamProject = witem.Project.Name;
string childTaskTitle = "Resolve bug " + bugID + " - " + bugTitle;
if (CreateResolutionTask(wiStore, bugID, childTaskTitle))
{
witem = CreateWorkItem(wiStore, teamProject, bugID, bugTitle, bugAssignedTo, bugAreaPath, bugIterationPath);
if (IsValid(witem))
{
witem.Save();
LinkParentAndChild(wiStore, witem, bugID);
}
}
}
private static bool IsValid(WorkItem witem)
{
ArrayList validationErrors = witem.Validate();
return validationErrors.Count == 0;
}
private static void LinkParentAndChild(WorkItemStore wiStore, WorkItem witem, int bugID)
{
var linkType = wiStore.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Hierarchy];
var parentWorkItem = wiStore.GetWorkItem(bugID);
int taskID = witem.Id;
var childWorkItem = wiStore.GetWorkItem(taskID);
parentWorkItem.Links.Add(new WorkItemLink(linkType.ForwardEnd, childWorkItem.Id));
parentWorkItem.Save();
}
private static WorkItem CreateWorkItem(WorkItemStore wiStore, Project teamProject, int bugID, string bugTitle, string bugAssignedTo, string bugAreaPath, string bugIterationPath)
{
WorkItemTypeCollection workItemTypes = wiStore.Projects[teamProject.Name].WorkItemTypes;
WorkItemType wiType = workItemTypes["Task"];
WorkItem witem = new WorkItem(wiType);
witem.Fields["System.Title"].Value = "Resolve bug " + bugID + " - " + bugTitle;
witem.Fields["System.AssignedTo"].Value = bugAssignedTo;
witem.Fields["System.AreaPath"].Value = bugAreaPath;
witem.Fields["System.IterationPath"].Value = bugIterationPath;
witem.Fields["Microsoft.VSTS.Common.Activity"].Value = "Bug Resolution";
return witem;
}
private static bool CreateResolutionTask(WorkItemStore wiStore, int bugID, string childTaskTitle)
{
WorkItem parentBug = wiStore.GetWorkItem(bugID);
WorkItemLinkCollection links = parentBug.WorkItemLinks;
foreach (WorkItemLink wil in links)
{
if (wil.LinkTypeEnd.Name == "Child")
{
WorkItem childTask = wiStore.GetWorkItem(wil.TargetId);
if ((childTask.Title == childTaskTitle) && (childTask.State != "Closed"))
{
return false;
}
}
}
return true;
}
private static Uri GetTFSUri(TeamFoundationRequestContext requestContext)
{
var locationService = requestContext.GetService<TeamFoundationLocationService>();
return new Uri(locationService.GetServerAccessMapping(requestContext).AccessPoint + "/" + requestContext.ServiceHost.Name);
}
private static WorkItemStore GetWorkItemStore(TeamFoundationRequestContext requestContext)
{
NetworkCredential netCred = new NetworkCredential(
"<username>",
"<password>");
WindowsCredential windowsCred = new WindowsCredential(netCred);
var credentials = new TfsClientCredentials(windowsCred);
credentials.AllowInteractive = true;
var tpc = new TfsTeamProjectCollection(
GetTFSUri(requestContext),
credentials);
tpc.Authenticate();
return tpc.GetService<WorkItemStore>();
}
public Type[] SubscribedTypes()
{
return new Type[1] { typeof(WorkItemChangedEvent) };
}
}
}