|
|
-
In an add-in for Outlook that I’m developing I wanted to show the built in Color Categories dialog so that the user could choose categories in a familiar way. The categories should be set on an object that is unique for the add-in (aka not for a mail or calendar item). I couldn’t find any way to show the dialog itself and when searching Internet I found nothing useful, but I have found an acceptable workaround. On the AppointmentItem (or one of the others) there is a method called ShowCategoriesDialog which shows the dialog, so what I did was to create a temporary appointment item, setting my categories, showing the dialog, read the Categories property and then finally cleaning up. Here is the code: private void ShowAllCategories()
{
Microsoft.Office.Interop.Outlook.Items items = IRM.TimeManager.Outlook.Globals.ThisAddIn.Calendar.Items;
Microsoft.Office.Interop.Outlook.AppointmentItem item = (Microsoft.Office.Interop.Outlook.AppointmentItem)items.Add();
try
{
item.Categories = MyObject.Categories ?? string.Empty;
item.ShowCategoriesDialog();
MyObject.Categories = item.Categories;
}
finally
{
if (item != null)
{
item.Delete();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(item);
}
if (items != null)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(items);
}
}
|
-
When using ClickOnce for distributing an Office Add-in the default configuration is to check for updates every 7 days. In an add-in I built I have situations where the add-in must be up to date (for example when changing the WCF service interface). In this post I will show how I solved it. To check for existing updates I use the regularly ClickOnce API:s, for example a simple check could look like this: return ApplicationDeployment.IsNetworkDeployed && ApplicationDeployment.CurrentDeployment.CheckForUpdate();
If there is a new version available the solution is to quit Outlook and start another small updater program which will start the installation and then restart the application (Outlook in my case). I have called the little program OutlookRestarter and is a simple Windows Application which is created as its own project in the solution. The compiled output from that project, I have added to the add-in project as a linked file and the important part is to set the file as Content in the Build Action so that it will be included in the ClickOnce installation.
To start the OutlookRestarter I use a simple Process.Start and the only “hard” part about it is to resolve the folder in which the files are deployed:
Uri updateLocation = ApplicationDeployment.CurrentDeployment.UpdateLocation;
Assembly addinAssembly = Assembly.GetExecutingAssembly();
string cachePath = addinAssembly.CodeBase.Substring(0, addinAssembly.CodeBase.Length -
System.IO.Path.GetFileName(addinAssembly.CodeBase).Length);
Uri restarterPath = new Uri(cachePath + "OutlookRestarter.exe");
Process.Start(restarterPath.AbsoluteUri, updateLocation.AbsoluteUri.Replace("File.vsto", "Setup.exe"));
Globals.ThisAddIn.Application.Quit();
I pass the Url to the installation as an argument to OutlookRestarter and the only notable thing about it is that I replace vsto, with the bootstrapper exe file, because I get ClickOnce errors if trying to go for the vsto file directly.
The OutlookRestarter application have three tasks; download the setup, run the setup and start Outlook again. I choose to download the file and run it manually to save the user from one click, which would be the effect if just doing a Process.Start on the Uri. For downloading the file I copied the DownloadFile method from http://www.codeguru.com/columns/dotnettips/article.php/c7005, with a small modification. Before creating the file I check if it already exists and in that case deletes it.
//Download setup
string tempFileName = Path.Combine(Path.GetTempPath(), "Setup.exe").ToString();
DownloadFile(args[0], tempFileName);
//Start and wait for setup to finish
System.Diagnostics.Process setupProcess = System.Diagnostics.Process.Start(tempFileName);
setupProcess.WaitForExit();
//Check to see if any existing Word Processes exist
System.Diagnostics.Process[] currentOutlookProcesses =
System.Diagnostics.Process.GetProcessesByName("OUTLOOK");
//Empty Case gets ignored, lazy but effective enough
foreach (System.Diagnostics.Process p in currentOutlookProcesses) p.WaitForExit();
//Start Outlook
StartOutlook();
Before starting Outlook again I wait for setup to finish and also makes sure that Outlook is closed. To start Outlook I check the registry settings that is created by Office 2010 (14), but for Office 2007 (12) I make an assumption that it is installed under Program Files.
private static void StartOutlook()
{
//Office 14 has installation path in the registry
RegistryKey localMachine = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Office");
object installationPath = null;
string installationFolder = null;
foreach (string s in localMachine.GetSubKeyNames())
{
installationPath = Registry.GetValue(string.Format(@"{0}\{1}\{2}", localMachine.ToString(), s, @"Outlook\InstallRoot"), "Path", null);
if (installationPath != null) break;
}
if (installationPath != null)
installationFolder = installationPath.ToString();
//Guess that Office is installed under Program Files
if (installationFolder == null || !File.Exists(Path.Combine(installationFolder, "Outlook.exe"))) //Office 12
{
installationFolder = Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft Office\\Office12");
if (!File.Exists(Path.Combine(installationFolder, "Outlook.exe")))
installationFolder = Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Office\\Office12");
}
string filePath = Path.Combine(installationFolder, "Outlook.exe");
if (File.Exists(filePath))
{
System.Diagnostics.Process.Start(filePath);
}
}
|
-
I had some really frustrating hours with the new version of the Reporting Services Report Web Viewer today. We are using local reports, which we actually create dynamically (subject of another post), but we couldn’t get them to be showed by the Report Web Viewer. We started to seek the problem way off, because we of course assumed that the problem had to do with our creating of the report. Finally we took a step back and tried to get some really simple things to work, but with the same problem. The behavior was that the report viewer continuisly kept refreshing/posting for the report but never displaying it. After some searching I found this blog “Reports Never Stop Loading With VS 2010” and it turns out that the behavior of v9/VS2008 and v10/VS2010 is totally changed. The new version doesn’t use frames and HTTP handler anymore, but instead uses the AJAX support in ASP.NET. This means that the request for the report returns to the aspx page hosting the report viewer, but we did not check for IsPostback before initializing the viewer with the report and data which made us restart the process over and over again. Read the blog post above for details.
|
-
Since we are developing a product, we need to support both Windows authentication (for most installations), and Forms Authentication for smaller installations or cases where the customer does not use Active Directory for whatever reason. A design goal we had for this was to only change the needed configuration in one place, and that ended up being the authentication tag in web.config. The solution turned out to be simple. What we did was to use initParams of the Silverlight host to set the configured value: <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<…>
<param name="initParams" value="authenticationMode=<%= (int)((System.Web.Configuration.AuthenticationSection)System.Configuration.ConfigurationManager.GetSection("system.web/authentication")).Mode %>" />
</object>
When the application starts we sets up the WebContext depending on configured value of the authenitcation mode:
private void InitializeWebContext()
{
const int Windows = 1;
const int Forms = 3;
// Create a WebContext and add it to the ApplicationLifetimeObjects
// collection. This will then be available as WebContext.Current.
WebContext webContext = new WebContext();
int authenticationMethod = Host.InitParams.ContainsKey("authenticationMode") ? int.Parse(this.Host.InitParams["authenticationMethod"], CultureInfo.InvariantCulture) : Windows;
if (authenticationMethod == Windows)
webContext.Authentication = new WindowsAuthentication();
else if (authenticationMethod == Forms)
{
webContext.Authentication = new FormsAuthentication();
}
else
throw new NotSupportedException();
this.ApplicationLifetimeObjects.Add(webContext);
// This will enable you to bind controls in XAML files to WebContext.Current
// properties
this.Resources.Add("WebContext", WebContext.Current);
}
In both cases we load the user when our main page is created.
WebContext.Current.Authentication.LoadUser(UserLoaded, null);
This will succeed if Windows Authentication is used or if the user have chosen “Remember Me” feature of the Forms Authentication. If the user isn’t loaded and Forms Authentication is configured, we then shows a login dialog prompting for user name and password. After closing it we call UserLoaded again so that our event UserAuthenticated is executed.
private void UserLoaded()
{
if (!WebContext.Current.User.IsAuthenticated && WebContext.Current.Authentication is FormsAuthentication)
{
LoginView loginWindow = new LoginView();
loginWindow.Closed += delegate(object sender, EventArgs e) { if (loginWindow.DialogResult == true) UserLoaded(); };
loginWindow.Show();
}
else if (WebContext.Current.User.IsAuthenticated)
{
OnUserAuthenticated(EventArgs.Empty);
}
}
|
-
I blogged about localizing a Silverlight UI here, and in that post I described how to create a StringFormatValueConvert. What I have totally missed though is that one of the new features of data binding in Silverlight 4 is support for StringFormat (together with FallbackValue and TargetNullValue), but when changing my code to use that I was back to getting US English formatting. What is happening? First I discovered that the UICulture property was set to “en-US” instead of “sv-SE” ad Culture was. I’m not sure why they are different, but that was easily solved by setting UICulture in when the Application is created. To my surprise that wasn’t enough. So now I really started to wonder what is happening? After searching a while I found this blog post by Rick Strahl about WPF Binding and Current Culture Formatting. As he describes there all WPF/Silverlight elements includes a Language property which does not default to Culture or UICulture! (Who did that design choice?) So the solution is almost as in Rick’s post for Silverlight to with a small modification and unfortunately you can’t do it by overriding the metadata in application startup since that seems to be missing in Silverlight. I therefore use this before InitializeComponent in my main page: public partial class MainPage : UserControl
{
public MainPage()
{
this.Language = System.Windows.Markup.XmlLanguage.GetLanguage(System.Globalization.CultureInfo.CurrentCulture.ToString());
InitializeComponent();
}
}
|
-
… is the title of a great post by Vibro that can be found here. This is an important subject and in all project where I have used claims and STS I also wanted the claims on the client. When cracking my own STS (before WIF) I always used the display token to get them from the identity provider (IP, aka the STS) so that seems to be in the line with Vibros thoughts. Just recently I tried to create a Silverlight application that was supposed to be an active client for a STS (as in the example of the WIF labs), but it failed on the current implementation requirements to use https protocol. What I would have wanted was the possibility to encrypt the username token with the public part of the STS in some cases and use https in some cases depending on the configuration of the installation. What we ended up with was to use the standard RIA support for authentication, but extending the User object with claims so that the client could hide/show functionality that the server anyway would deny the user. We also chose to use IClaimsPrincipal and IClaimsIdentity on the server side even if the claims is created by the server itself. This will make it easier for us to move to a STS when the tooling for Silverlight becomes a little bit more mature. Anyone else out there having experience with using WIF and Silverlight? I would love to here about your experience in that case.
|
-
This is the third post in a series of X. Read the first one here about out overall design and the second one here about localizing the UI. After doing some basic work on our repositories we wanted to create a Unit of Work implementation that would call SaveChanges on our EF ObjectContext and also commit the transaction if one is used. The problem was when do we create the Unit of Work? This probably sound like a stupid question if you haven’t used WCF RIA Services. The way WCF RIA Services works is that you create three methods (InsertXxx, UpdateXxx and DeleteXxx) to implement support for adding, changing and deleting whatever your service is exposing. They are called for respective operation with the entries in the ChangeSet that WCF RIA Services uses. So when should you create the Unit of Work? When you call SubmitChanges on the DomainContext in the Silverlighet client it ends up in the Submit method of the DomainService which executes the following methods: - AuthorizeChangeSet
- ValidateChangeSet
- ExecuteChangeSet
- PersistChangeSet
It is the PersistChangeSet that calls each of the three Insert-, Update- and Delete-methods. After trying out a couple of different designs we decided to actually see the DomainService as the Unit of Work and this decision made it all a lot simpler (and simple is good). The solution we did was to create our own base class for all DomainServices that we call TransactionDomainService. This class is completely generic and could be used in any WCF RIA Services project and it supports integration testing. public abstract class TransactionDomainService : DomainService
{
private bool persistInTransaction;
private TransactionScope transaction;
public TransactionDomainService() : this(true, true)
{
}
public TransactionDomainService(bool persistInTransaction, bool autoCommit)
{
this.persistInTransaction = persistInTransaction;
this.AutoCommit = autoCommit;
}
public bool AutoCommit { get; set; }
protected internal TransactionScope Transaction
{
get { return this.transaction; }
set { this.transaction = value; }
}
/// <summary>
/// Commits the active transaction if <see cref="AutoCommit"/> is <b>true</b>.
/// </summary>
protected void Complete()
{
Complete(this.AutoCommit);
}
/// <summary>
/// Commits the active transaction.
/// </summary>
/// <param name="commit"><b>true</b> to commit the active connection; otherwise <b>false</b>.</param>
protected internal virtual void Complete(bool commit)
{
if (commit && transaction != null)
transaction.Complete();
}
protected override bool PersistChangeSet()
{
if (persistInTransaction && this.transaction == null)
this.transaction = new TransactionScope();
bool result = base.PersistChangeSet();
this.Complete();
return result;
}
protected override void Dispose(bool disposing)
{
try
{
if (transaction != null)
{
transaction.Dispose();
transaction = null;
}
}
finally
{
base.Dispose(disposing);
}
}
}
The core to this class is that we override PersistChangeSet so that we can wrap that call in a TransactionScope. After the call to base.PersistChangeSet we Complete the transaction which normally means that we call Complete on the TransactionScope, but when doing integration tests the AutoCommit flag will be set to false. This also works very well for other scenarios than CUD, even though we don’t wrap it in a using statement that I have normally used.
But wait … wasn’t the Unit of Work also supposed to take care of calling SaveChanges on the EF ObjectContext too? Sure, but we choose to implement this in another base class.
public abstract class ObjectContextDomainService : TransactionDomainService
{
IRepository repository;
public ObjectContextDomainService(IRepository repository) : base()
{
if (repository == null)
throw new ArgumentNullException("repository");
this.repository = repository;
}
/// <summary>
/// Calls SaveChanges on the object context and commits the transaction.
/// </summary>
/// <param name="commit"><b>true</b> to commit the active connection; otherwise <b>false</b>.</param>
protected internal override void Complete(bool commit)
{
repository.ObjectContext.SaveChanges();
base.Complete(commit);
}
}
This class might be a little bit more specific to our implementation than TransactionDomainService. All our repository interfaces inherits a generic IRepository interface and in that we expose a IObjectContext that we have used to wrap the real ObjectContext. We more or less used the basic idea that Fredrik Normén blog about in “Aspen – A sample app using Silverlight 4 and .NET 4 – part 3 of X – Repository implementation”.
Hope this helps.
|
-
This is the second post in a series of X. Read the first one here about our overall design. Since we will develop a product that will be sold in more countries in a not to far future it is important to make sure that it can be localized. In Silverlight the key to localization is (as many other things in SL) data binding. This different for developers that are used to Windows Forms, but it is still very easy and it is still resource files that are used so lets start there. First step is to add a resource file and I usually create a Resources folder in my project in which I put a new resource (resx) file. There is one important step to do in the resource editor that I have never done before and that is to set the Access Modifier to public (Internal is default). Even though it is set to public, the constructor of the class is generated as internal, which makes it hard to instantiate the class in XAML. Instead we choose to expose the resource in our ViewModel base class. [Display(AutoGenerateField = false)]
public Resources.Resource Resource { get { return resources; } }
The Display attribute is from data annotations and we put it there to make sure that the property never should show up when using auto generation in the controls that supports it. With this in place we can bind to it in XAML (and we have our ViewModel as data context for the view/user control). <Button Content="{Binding Path=Resource.Save, FallbackValue=Spara}" />
The first part in the path refers to the Resource property in the view model and the second part is the name of the string in the resource file. We also set FallbackValue to have something shown in the designer.
We will also use data annotations extensively. This means that we need to make sure that the annotations will use localized strings for everything. Lets start by looking at some more code. [Display(Order = 1, ResourceType = typeof(Resources.Resource), Name = "ProcessGroup")]
[Required(ErrorMessageResourceType = typeof(Resources.Resource), ErrorMessageResourceName = "RequiredErrorMessage")]
public int ProcessGroupId { get; set; }
Here we use two data annotation attributes; Display and Required. The Required attributes of course means that the property is required to be set and the Display attribute has information about how this property should be handled when using auto generation in for example the data grid. Both attributes also uses a resource file to get the error message and the caption for the property in auto generation. When WCF RIA Services tooling auto generates this class on the client side the resource is not available there, but it will have to be. To solve this you add a structure that will give the same namespace as the generated code, in our case this means adding a Web folder and under that a Resources child folder. In this folder we then adds an existing item, but we add it as a link so that changes on the client is immediately available in the client.
There is also a DisplayFormat attribute that controls formatting of values, even though this does not seem to get handled by the data grid. For example we use this for dates: [DisplayFormat(DataFormatString = "{0:d}")]
To work around that you could easily create a value converter, for example: public class StringFormatValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter == null) return value;
string formatString = parameter.ToString();
if (string.IsNullOrEmpty(formatString)) return value;
return string.Format(System.Globalization.CultureInfo.CurrentCulture, formatString, value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
This converter can then be used in the Binding element in XAML. When we use auto generation in for example the data grid we need to catch the AutoGeneratingColumn and add the formatter to the binding. private void dataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "Imported")
{
DataGridTextColumn textColumn = e.Column as DataGridTextColumn;
if (textColumn != null)
{
textColumn.Binding.ConverterParameter = "{0:d}";
textColumn.Binding.Converter = new Converters.StringFormatValueConverter();
}
}
}
Finally we need to control the culture of the web project by adding the globalization information to web.config or the hosting page. <globalization culture="sv-SE" uiCulture="sv-SE" />
Another possibility is to use the enableClientBasedCulture attribute of the globalization element. It is also possible to set it just for the Silverlight object by setting uiculture and culture param for the object in the Silverlight test aspx page. In all cases it probably would be nice to allow the user to control which culture that should be used, either in a web page or in a settings dialog of the Silverlight application.
|
-
I’m helping a software development company to “convert” a VB6 application to .NET. We have a very tight schedule and originally planned to do a convert with some help from the Conversion Wizard and some parts manually, but still close to their current version. The goal was to just get over to .NET and from there develop the product that they and their customers wanted, but because of a big chance for them to get a new really big customer, the plans changed. The big customer wants a web based application since there are many users, so we decided to switch to a Silverlight client. After doing some spikes and proof of concepts elaboration we have now settled on a design where we hope to be very productive. We are using the following technology: This seems very new all over the place, but we will also use Enterprise Library 4.1 to do classic data access so that we can reuse a lot of SQL code from the VB6 application. First we had a design without WCF RIA Services and just using plain WCF Services, but after some elaboration we found that WCF RIA Services would help us a lot. Many videos and examples that uses WCF RIA Services also uses Entity Framework and then exposing the domain model to the client. We did not want to expose the domain model to the client, because the domain model will have other perspectives on its design than the user interface. Early on we decided to use Model-View-ViewModel (MVVM) for the user interface (actually this was the plan even for the Windows Forms client) to get a good separation of concerns and increasing testability. This pattern really shines with WPF and Silverlight clients, since these platforms have powerful data bindings and also support for Commands (in Silverlight 4).  Every time I’ve used the MVVM pattern before I have always only exposed the View Model to the View, but this time we choosed to actually expose the Model to. I had some thought about this, but finally settled on seeing the Model as a Presentation Model, aka a model designed for the View. There are a lot of time to save and powerful tooling support to use that was part of the reason to this choice, because it fits really nice with WCF RIA Services and the data annotations in .NET 4.0. This is why I see the WCF RIA Services as a part of the client. The services themselves are also designed from the use cases and tightly coupled to the client (there are for example no SOA thinking in these services). The data transfer objects (DTOs) is designed to fit the View and are therefore a combination of DTOs and presentation model objects. This means that we tag the classes in the presentation model with data annotations. This classes are also generated by the WCF RIA Services tooling on the client with the same data annotations. When we bind the controls in the Silverlight views against these classes the controls picks up the validations and the display information (labels/captions) giving us productivity and not forcing us to repeat our self. The Server part is reusable and will probably have other clients in the future as a ASP.NET and/or WPF user interface (or why not even a Windows Phone 7 UI?).  We started with creating clean POCO classes for the domain model and then using the Entity Framework code first CTP 3 to map it against our database, but we have now switched to use the Entity Framework designer and downloaded the POCO generator through Visual Studio Extension Manager. The reason was mainly because we had a problem with mapping our relations (which is solved now) and when switching we found some productivity enhancement with using the designer (at least so far :~). The domain classes are mapped with the wonderful AutoMapper to the presentation model classes. When catching up on all this new technology I found great value in reading the extensive amount of blog posts that Fredrik Normén has written about Silverlight and WCF RIA Services so that is a great resource if you want to get started. He has also used DTOs/Presentation Model with WCF RIA Services and not exposing the domain model.
|
-
I bought a new laptop (DELL Precision M6500) a while ago. My idea was to run much of my work in virtual machines and just keep the parts that I use the most in the main operating system. So this is my story of setting this new machine up. Setting Up the Host and Main OS I installed Windows 7 64-bit with Forefront anti-virus and of course hooked it up with my Home Server for backups. I choose both Office 2010 and Visual Studio 2010, but SQL Server 2008 (not R2) for my main OS. Additionally I installed TweetDeck for twitter, Live Writer for blogging, Spotify for music and iTunes is required since I have iPhone. Getting the Old Machine Running on the New Machine To enable a fast switch, my idea was to create a virtual machine of my old one. I downloaded Disk2vhd from sysinternals.com which can take a snapshot of the disc even when it’s running. The process to create the file took a couple of hours, but when it was on the external hard drive it was easy to copy to my new machine. In the meanwhile I downloaded Windows Virtual PC. Booting up the old PC in Virtual PC was a big disappointment, because it can not handle 64-bit versions of OS and even my old PC was running Vista 64-bit. This is a big limitation in Virtual PC that I can’t understand, it could at least be available in Ultimate version of Windows? The next thing to try was downloading WMware Player (also free) which has support for 64-bit guest OS. I spent several days trying to get my image up and running in WMware Player, but it always ended with a blue screen of death before login screen should have shown up. It didn’t help to run repairs from the setup or anything. After a tip from a colleague I installed Virtual Box instead, which is open source and also free. My image booted immediately in Virtual Box, but I got problems with 100% CPU utilization. Trying different things with great support on the Virtual Box forums, finally got it working. Its not perfect, but I can boot it up and do some work in it, even if it still easily get high CPU utilizations. The settings that got it going was that I turned down from 2 CPU cores to just 1 and I set the RAM to something just below 4GB. With any of these parameters higher blocks the CPU completely. Creating More Virtual Machines After getting my new machine working, it was time to fulfill my plans on making virtual machines for different things. First thing to do was of course to set up a new Windows 7 guest, with all current Windows Updates and making sure it is activated. I also needed to spend some time to get the networking working as I wanted. I ended up with a single network card running NAT, but it required me to configure my host machine name in the hosts file and setting the DNS servers hard. With that completed I saved a copy of my clean OS installation to make it easy to set up as many of them as I need. Next step I choose to install only the client tools for SQL Server 2008 SP1, because I believe that it wont be necessary to have running databases more than on the host. Saving a new copy of the disk and then continuing with Office 2003 (just Excel, Outlook, PowerPoint and Word), SP2 and then a last copy of the disk. Note that there is no CPU utilizations problems with Virtual Box on this freshly installed machines. Now I have started to make a Visual Studio 2008 machine, and later on I will continue with a Visual Studio 2005 (need that for some WSE3 stuff that isn’t upgraded yet). A Windows Server 2008 with Sharepoint for development is also on the list. Have you done anything similar and want to share your ideas or what have worked for you?
|
-
Three years ago (can’t believe it’s been so long) I blogged about creating an Outlook add-in. The add-in was developed with VSTO 2, but now I thought it was time to upgrade it to VSTO 3 and try out the ClickOnce deployment. If I was impressed about how easy and much better it had become to develop for Outlook 2007 with VSTO 2, I must say that the developing with VSTO 3 is a lot better. The biggest problem in VSTO 2 was deployment with many security problems but in VSTO 3 that is elegantly solved. It is just as easy as doing a regular ClickOnce installation with a few minor limitations. Compared to doing a ClickOnce deployment with Windows Forms you can’t choose which files that should be included, but rather all locally copied references are included in the setup. This means that you probably will have to change that for a couple of your references because you have them in the GAC on your dev machine. On the web server you will have to set up a new MIME type for the .vsto file name extension. For a more detailed getting started guide you can read for example this on MSDN. Another nice new feature is that it has become much easier to create new regions on the built-in forms. As I blogged about here you needed to design the form in Outlook, import it to the project and add registry settings to get it to load, but now all you have to do is create a special windows forms user control, called Outlook Form Region in the Add-dialog. This makes it a lot easier to create form regions and removes a lot of plumbing code. There is no longer needed to do any registry modifications, it rather just works. References: General Experience with Outlook 2007 Add-in Development Adding a Property Page to Outlook 2007 Options Dialog Form Regions in Outlook 2007 (Part 1) Form Regions in Outlook 2007 (Part 2) Custom Task Panes in Outlook 2007
|
-
So I have created an account on twitter too now. You can follow me here: http://twitter.com/ericqu
|
-
I had a method that looked like this in my code: public string Translate(string entity, string field)
{
string key = Translator.GetKey(entity, field);
if (translationCache.ContainsKey(key))
return translationCache[key];
else
{
string res = this.Translator.Translate(entity, field);
translationCache.Add(key, res);
return res;
}
}
This code uses a cache mechanism that counts for most of the lines in the method. For a new requirement I needed to add an overloaded method that takes a single argument and calls an overloaded versions of Translator.GetKey and Translator.Translate methods, but the rest of the code would look the same. Of course I would like to keep the code DRY (Don’t Repeat Yourself). So how do you solve a problem when a single line of code in the middle of the method needs to be different, but the rest should be the same? Of course you could split the logic and create a couple of smaller methods that handles other parts of the code, but in this case it is hard to do that in a good way. Typically I would say that this code will be copied and pasted and then changing the line that needs to, but violating DRY. The way I solved the problem was to use a delegate as argument and invoke that on the line that differs. In this case I choose the Func<T>() delegate.
public string Translate(string name)
{
return Translate(Translator.GetKey(name), () => this.Translator.Translate(name));
}
public string Translate(string entity, string field)
{
return Translate(Translator.GetKey(entity, field), () => this.Translator.Translate(entity, field));
}
private string Translate(string key, Func<string> translation)
{
if (translationCache.ContainsKey(key))
return translationCache[key];
else
{
string res = translation.Invoke();
translationCache.Add(key, res);
return res;
}
}
Now I have a single private method that handles the caching part except for creating the key. The solution with taking the Func<string> delegate as a parameter is easily called by a using lambda expression in the public methods. Each expression calls the correct overloaded version of the Translate method.
Hope this helps you to find new ways to keep your code DRY, when extracting methods and other techniques, doesn’t fit that well.
|
-
I hade a really strange problem with a LINQ to SQL diagram today. I added an association between two classes and everything looked perfectly fine in the designer, but there were no code generated for the association. After some frustrating hours I found a blog post written by Bart Lannoeye that saved me. Read it since he also has very claryfing pictures. THe problem was that my classes had no primary keys since they were generated from views.
|
-
Windows Identity Framework (known as Geneva) has now been released as a release candidate (RC). This is great and will have a big impact on how identity and some other security related parts of your applications will be implemented. If yu haven’t started use claims yet, now is the time to do some reading to catch up and shift yuor mind. Vibro’s blog is a great source, as well as Dominick Baier, Keith Brown and hopefully you will also find some useful material on this blog (look in the Security Token Service tag).
|
|
|
|