I've had to write my first Outlook add-in.
Basically, I have two signatures to choose from: "oferta" and "default". Depending on the words contained in the mail subject, a different signature will be used.
Everything works fine with text-only signatures, but when pictures are included, these are never sent and ares displayed as blank squares instead.
However, if I manually select any of the signatures in Outlook, the pictures are properly displayed.
I guess the problem is in the GetSignature() method, which I borrowed form someone else's answer (sorry, I can't find where I got this from!).
How could I solve this? Is there a better way to automatically change the signatures?
This is my code:
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
// When an email is sent a different signature is appended depending on the subject.
private void Application_ItemSend(object Item, ref bool Cancel)
{
MailItem mail = (MailItem)Item;
string subject = mail.Subject;
string firma = subject.ToUpper().Contains("PEDIDO") ? GetSignature("oferta") : GetSignature("default");
mail.HTMLBody += firma;
if (mail != null) Marshal.ReleaseComObject(mail);
}
// Finds and returns the .htm signature file.
private string GetSignature(string signatureName)
{
string appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Microsoft\\Signatures";
string signature = string.Empty;
DirectoryInfo diInfo = new DirectoryInfo(appDataDir);
if (diInfo.Exists)
{
FileInfo[] fiSignature = diInfo.GetFiles(signatureName + ".htm");
if (fiSignature.Length > 0)
{
StreamReader sr = new StreamReader(fiSignature[0].FullName, Encoding.Default);
signature = sr.ReadToEnd();
if (!string.IsNullOrEmpty(signature))
{
string fileName = fiSignature[0].Name.Replace(fiSignature[0].Extension, string.Empty);
signature = signature.Replace(fileName + "_files/", appDataDir + "/" + fileName + "_files/");
}
}
}
return signature;
}
}
Of course - pictures are added as separate attachments, and you never deal with them.
Also, concatenating two HTML strings (mail.HTMLBody += firma;) does not necessarily produce a valid HTML string.
If using Redemption is an option (I am its author), it exposes RDOSignature.ApplyTo method, whcih inserts a signature including its attachments and styles.
Related
My team has been using VSTS for 8 months. Now, Our customer is asking to get "Repro Steps" of the work items in VSTS.
Is there any way to get the content of "Repro Steps" without the HTML format?
No, because the Repro Steps value is the rich text that can contain image etc…. So, the value is incorrect if just return the data without HTML format.
However, you can remove HTML tag programing.
Simple code:
public static string StripHTML(string input)
{
return Regex.Replace(input, "<.*?>", String.Empty);
}
var u = new Uri("[collection URL]"");
VssCredentials c = new VssCredentials(new Microsoft.VisualStudio.Services.Common.WindowsCredential(new NetworkCredential("[user name]", "[password]")));
var connection = new VssConnection(u, c);
var workitemClient = connection.GetClient<WorkItemTrackingHttpClient>();
var workitem = workitemClient.GetWorkItemAsync(96).Result;
object repoValue = workitem.Fields["Microsoft.VSTS.TCM.ReproSteps"];
string repoValueWithOutformat = StripHTML(repoValue.ToString());
I have found some plugin code on the web that enables me to get the entity ID and the object type code for an entity in a plugin. The plugin is fired on RetrieveMultiple on activitypointer. The code lets me get the id and object code of the entity that is currently being viewed (which is displaying the activities grid which is firing the plugin).
This code works fine when using the web interface. However I need it to also work in the Outlook preview pane and currently it does not. The activities grid in the Outlook preview pane just says "an error has occurred". Below is the code that the plugin is using to get the details from the web header.
internal static Dictionary<string, string> GetHeaderFields(HttpContext webcontext, string objectTypeCode, string objectId)
{
Dictionary<string, string> fields = new Dictionary<string, string>();
string callerentitytype = null;
string callerentityidstring = null;
try
{
// Activities Navigation Pane
if (new List<string>(webcontext.Request.Params.AllKeys).Contains("oType"))
{
callerentitytype = webcontext.Request.Params["oType"];
callerentityidstring = webcontext.Request.Params["oId"];
}
// Activities Sub Grid
else
{
string requeststring = webcontext.Request.UrlReferrer.Query;
requeststring = requeststring.Substring(1);
string[] parts = requeststring.Split(new string[] { "=", "&" }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < parts.Length - 1; i++)
if (parts[i].ToLower() == "otype" || parts[i].ToLower() == "etc")
callerentitytype = parts[i + 1];
else if (parts[i].ToLower() == "oid" || parts[i].ToLower() == "id")
callerentityidstring = parts[i + 1];
}
fields.Add(objectTypeCode, callerentitytype);
fields.Add(objectId, callerentityidstring);
}
catch (Exception ex)
{
throw new Plugin.LoggableException(string.Format("Failed to obtain header information; {0}", ex.Message), ex.InnerException);
}
return fields;
}
The reason is that webcontext.Request.UrlReferrer is NULL. Is there anywhere else I can get this info of the 'calling' entity? (Not the activity sub grid that is triggering the plugin, but the actual parent entity that the sub grid is on).
Thanks for any help or direction with this.
This might work. Each of the activitypointers that are returned should all be "regarding" the same record (if in a sub grid). If you take say the 1st one and examine the regardingobjectid property, that should be an entity reference which will give you the logical name of the parent and it's guid. If that works, it will work across all clients (in theory anyway).
Im using VS to develop a windows phone app. Im doing it wp8 but it doesnt matter because it the code works for 7 too. Anyway, I have a text box and a button. When the text from the text box is entered, and the button is clicked it adds that to isolated storage.
On my other page, I have a textblock. Which should display what I wrote in the text box. It does work, but first let me sho you my code.
if (appsettings.Contains("name"))
{
appsettings.Remove("name");
appsettings.Add("name", TitleTextBox.Text); //rename if already exists
}
and then the second page that collects the info is below.
if (appsettings.Contains("name"))
{
string content = appsettings["name"].ToString(); //converts to string
titleTextBlock.Text = content; //shows title in text block
}
The problem is, the "name" works. However, if I call it ANYTHING else it does not. I want to add a different name because i want to be able to input two lots. For example two text box's and then when you press the button and go to the other page, it has two textblocks displaying each string in each one. I can't seem to do this because only "name" works. Ive changed it to other names and it doesnt work. Does anyone know why?
IsolatedStorageSettings works as a Dictionary. If you want to acces a specific key it should exist in the Dictionary.
If you try to change the value that already exists you can do like this:
if (appSettings.Contains("key")) appSettings["key"] = "new value";
else appSettings.Add("key", "new value");
Don't also forget to save your appSettings:
appSettings.Save();
And also according to your code - in ISS you can put not only string - it can be any object, if you want to get it, you should make a cast or use as:
string content = (string)appsettings["name"]; //converts to string
string content = appsettings["name"] as string;
EDIT - after comments, rebuild once more
If you want to have a to-do-list and you know that every task has its specific title, description and time then I would advise to create a special class for this, for example:
public class myTodo
{
public string TaskTitle { get; set; }
public string TaskDescription { get; set; }
public TimeSpan ElapsedTime { get; set; }
}
I used TimeSpan because I think it's easier to manage Time with it. Then if you want to Save/Load your myTodo you can do like this:
// create an example of your task
myTodo newTask = new myTodo() { TaskTitle = "Clean", TaskDescription = "Clean room", ElapsedTime = new TimeSpan(2, 0, 0) };
// add it to ISS and save
if (appSettings.Contains("firatTask")) appSettings["firatTask"] = newTask;
else appSettings.Add("firatTask", newTask);
appSettings.Save();
// try to load
myTodo read = appSettings["firatTask"] as myTodo;
You can access your item like this:
read.Title = TitleTextBox.Text; // and so on
Consider also making a List<myToDo> and be aware that ISS shoul also handle this:
List<myTodo> listJob = new List<myTodo>();
listJob.Add(firstTask); // firstTask is myToDo
listJob.Add(secondTask); // secondTask is myToDo
if (appSettings.Contains("listTask")) appSettings["listTask"] = listJob;
else appSettings.Add("listTask", listJob);
appSettings.Save();
List<myTodo> readList = appSettings["listTask"] as List<myTodo>;
Ok right now I have an object containing 3 strings, along with setters and getters. Now I have two questions -
First, I'm new to C# is there any way to optimize the following methods and make them more efficient?
void getSearchResults(object sender, RoutedEventArgs e)
{
string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAPPID>&query=%22";
string companyName = ((TaxiCompany)sender).CoName;
string formatAndKey = "%22&sources=web";
WebClient c = new WebClient();
c.DownloadStringAsync(new Uri(baseURL + companyName + formatAndKey));
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
}
//Parses search XML result to find number of results
void findTotalResults(object sender, DownloadStringCompletedEventArgs e)
{
lock (this)
{
string s = e.Result;
XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s)));
String results = "";
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("web:Total"))
{
results = reader.ReadInnerXml();
break;
}
}
}
}
}
Second, I'm initializing an object - new Taxi Company (String name, String Phone, String Results). I've got name and number and I need to call the above two functions to get noOfResults so that I can initialize the object. However, I seem to run into a bunch of issues with the event handlers.
I've primarily been a web dev, so there might be something really basic I'm missing here. I have a feeling setting up the bing methods to return a string back to the constructor might be th easiest, but not quite sure how.
First of all, you don't need the lock on the main page. Then, I would say that your XmlReader block should be replaced with the LINQ-to-XML variation called XDocument, that will allow you to access the XML document with a single, elegant line:
XDocument doc = XDocument.Parse(e.Result);
Once you have the document, you can check whether it contains a specific XNode.
I need to read stuff from an Outlook msg file. Currently I'm using a class from CodeProject.com project to accomplish this, since deploying VSTO and Outlook on a server is not an option.
This class gets To, From, CC, Subject, Body, and everything else I need from the msg file, except Date information (such as Received Date and Sent Date).
There is some (really, really low-level) documentation on how to get stuff out of msg files on MSDN, but it's a little beyond the scope of this project and doesn't mention dates at all.
Ideally I'd be able to have a drop-in replacement for the class I am using now (OutlookStorage.cs in the previously mentioned CodeProject) or be able to modify the existing class a bit. To modify, I would need the correct 4 character hexidecimal prop identifier for received date. For instance, Subject is listed as PR_SUBJECT = "0037" and Body is listed as PR_BOY = "1000".
If you're using OutlookStorage.cs from CodeProject, then add the following:
private const string PR_RECEIVED_DATE="007D";
private const string PR_RECEIVED_DATE_2 = "0047";
...
/// <summary>
/// Gets the date the message was received.
/// </summary>
public DateTime ReceivedDate
{
get
{
if (_dateRevieved == DateTime.MinValue)
{
string dateMess = this.GetMapiPropertyString(OutlookStorage.PR_RECEIVED_DATE);
if (String.IsNullOrEmpty(dateMess))
{
dateMess = this.GetMapiPropertyString(OutlookStorage.PR_RECEIVED_DATE_2);
}
_dateRevieved = ExtractDate(dateMess);
}
return _dateRevieved;
//return ExtractDate(dateMess);
}
}
private DateTime _dateRevieved = DateTime.MinValue;
private DateTime ExtractDate(string dateMess)
{
string matchStr = "Date:";
string[] lines = dateMess.Split(new String[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
if (line.StartsWith(matchStr))
{
string dateStr = line.Substring(matchStr.Length);
DateTime response;
if (DateTime.TryParse(dateStr, out response))
{
return response;
}
}
}
return DateTime.MinValue;
}
I think the Aspose library will do what you want, ok it a 3rd party lib so may not be what you want. There are a few vbs scripts around that get basic infomation out of msg files that could be translated.
Got a hint from this:
string fullFileName = "c:\message.msg";
DateTime dateRevieved = new DateTime();
StreamReader sr = new StreamReader(fullFileName, Encoding.Default);
string full = sr.ReadToEnd();
string date;
int iStart;
int iLast;
string caption;
//This -should- handle all manner of screwage
//The ONLY way it would not is if someone guessed the -exact- to-the-second
//time that they send the message, put it in their subject in the right format
while (true) { //not an infinite loop, I swear!
caption = "Date:";
if (full.IndexOf("Date:") > -1) { //full shortens with each date is removed
string temp = "";
iStart = full.LastIndexOf(caption);
temp = full.Remove(0, iStart + caption.Length);
full = full.Substring(0, iStart);
iLast = temp.IndexOf("\r\n");
if (iLast < 0) {
date = temp;
} else {
date = temp.Substring(0, iLast);
}
date = date.Trim();
if (date.Contains(subject) || subject.Contains(date)) {
continue; //would only happen if someone is trying to screw me
}
try {
dateRevieved = DateTime.Parse(date); //will fail if not a date
break; //if not a date breaks out of while loop
} catch {
continue; //try with a smaller subset of the msg
}
} else {
break;
}
}
This is kind of a hack compared to the ways you can get other things from msg files using something this lovely project. Still, it's stood up to everything I have thrown against it, and as noted the -only- way to fool it is to put the exact to-the-second date in the subject line in the proper format.
to combine your two posts I would suggest the following solution:
To modify, I would need the correct 4 character hexidecimal prop identifier for recieved date. For instance, Subject is listed as PR_SUBJECT = "0037" and Body is listed as PR_BOY = "1000".
Look for "007D".
Use the method you posted in your second post on the received data to eliminate the problem when the same (date) string is inside the subject.
I have to mention that this method doesn't seem to work on internal eMails: In mails I receive from colleagues, there is no substg1.0_007Dxxxx-Property.
Here, the date seems to be hidden in substg1.0_0047xxxx.
All the best!
inno