MVC 5 T4 Templates and View Model Property Attributes


Yesterday, I published a blog post demonstrating how to modify your T4 templates for your single project.  Today, I’m going to show you how to access an attribute from your Model in your T4 template.  Beginning with MVC 5, the T4 templates no longer use ModelProperty class to iterate over the properties in your Model.  ModelProperty has direct access to Attributes, and there are plenty of resources on the web on how to read the attributes in your T4 using that.  In MVC 5, however, ModelProperty has been replaced with PropertyMetaData, which does not have access to the attributes of your properties.  Further, I found it very difficult to find a solution for MVC5 on the web, until finally I came across an old MSDN Magazine article, which was only helpful because I could download the sample project and take a look inside.  The article itself is missing referenced images and code samples.  So, I’m taking what I learned, and am creating this, hopefully simple, post to demonstrate.

For our purposes today, we will be extending our project from yesterday to make the Biography property on the Person model a RichText field.  So, for a reminder, here is our Person model:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
 
namespace CustomViewTemplate.Models
{     
     [Table("Person")]
     public class Person
     {
         [Key]
         public int PersonId { get; set;}

         [MaxLength(5)]
         public string Salutation { get; set; }

         [MaxLength(50)]
         public string FirstName { get; set; }

         [MaxLength(50)]
         public string LastName { get; set; }

         [MaxLength(50)]
         public string Title { get; set; }

         [DataType(DataType.EmailAddress)]
         [MaxLength(254)]
         public string EmailAddress { get; set; }

         [DataType(DataType.MultilineText)]
         public string Biography { get; set; }     
     }
}

To demonstrate that we can also use a custom attribute, let’s create one.  Create a class called RichTextAttribute in the project root directory:

namespace CustomViewTemplate
{
     [AttributeUsage(AttributeTargets.Property)]
     public class RichTextAttribute : Attribute
     {
         public RichTextAttribute() { }
     }
}

Then, back to PersonModel, add the RichText attribute to the Biography property:

[RichText]
[DataType(DataType.MultilineText)]
public string Biography { get; set; }

Now, copy the following global template files from

C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcView\

to a folder called

CodeTemplates\MvcView\

  • Create.cs.t4 (OR Create.vb.t4)
  • Imports.include.t4
  • ModelMetaDataFunctions.cs.include.t4 (OR ModelMetaDataFunctions.vb.t4)

Now, remember the PropertyMetaData, doesn’t have access to the attributes, so we need to create a new class with a function that does the work for us, so let’s create a class called T4Helpers, with a public function called IsRichText.

using System; 

namespace CustomViewTemplate
{
     public static class T4Helpers
     {
         public static bool IsRichText(string viewDataTypeName, string propertyName)
         {
             bool isRichText = false;
             Attribute richText = null;
             Type typeModel = Type.GetType(viewDataTypeName);

             if (typeModel != null)
             {
                 richText = (RichTextAttribute)Attribute.GetCustomAttribute(typeModel.GetProperty(propertyName), typeof(RichTextAttribute));
                 return richText != null;
             }

             return isRichText;
         }
     }
}

We are using our custom RichTextAttribute here, but you could use any Attribute you would like.  What we are doing is returning true or false; indicating that the given property either does or does not have the attribute we are checking for.

Before we can use this function in our T4 template, we’ll need to Build our project and then add a reference to our assembly in the imports file.

Open your projects copy of Imports.include.t4 and add a reference to your assembly as line 4.  For me, it looks like this:

<#@ assembly name="C:\My Applications\Playground\CustomViewTemplate\CustomViewTemplate\bin\CustomViewTemplate.dll" #>

As you are typing the line of code above, if you are using tangible T4 Editor, you might see an inellisense option like so:

image

and you might be tempted to use it, but there is a bug that will prevent this form working.

Okay, now we can modify our T4 template.  Open Create.cs.t4 and look for the foreach loop that iterates over our list of properties.  You’ll find this around like 96:

<# foreach (PropertyMetadata property in ModelMetadata.Properties) {      if (property.Scaffold && !property.IsAutoGenerated && !property.IsReadOnly && !property.IsAssociation) {          // If the property is a primary key and Guid, then the Guid is generated in the controller. Hence, this property is not displayed on the view.          if (property.IsPrimaryKey && IsPropertyGuid(property)) {              continue;          } #>

To avoid unnecessary calls to our new method, we’ll only check for the RichText attribute against properties that we will actually be rendering, so after the inner If statement (the one that checks for primary key’s and Guid’s), we can add this line:

bool isRichText = CustomViewTemplate.T4Helpers.IsRichText(ViewDataTypeName, property.PropertyName);

Now, the tricky part is using our new variable and finding the correct place for it.  For our purposes, we will put it inside the nested if statement that is checking for a checkbox.  So, that entire if block would look like this with our new code added:

             if (isCheckbox) {
#>


<div class="checkbox">
<#                  PushIndent("    "); #>
                 @Html.EditorFor(model => model.<#= property.PropertyName #>)
<#              } else if (property.IsEnum && !property.IsEnumFlags) { #>
                 @Html.EnumDropDownListFor(model => model.<#= property.PropertyName #>, htmlAttributes: new { @class = "form-control" })
<#              } else if (isRichText) { #>
                 // TODO: Place RichTextBox HTML here
<# } else { #>
                 @Html.EditorFor(model =>; model.<#= property.PropertyName #>, new { htmlAttributes = new { @class = "form-control" } })
<#              }           } else { #>
                 @Html.EditorFor(model => model.<#= property.PropertyName #>) 
<#          } #>

Now, you can see that I just put some generic text instead of an @Html helper, because the implementation details of RichText aren’t important for the purposes of this tutorial.

To verify everything is working properly, we can save changes, go to our PersonController and add a Create action, Right-Click inside to Add View, and add a Create view.

Inside the generated Create.cshtml, you can scroll down and see that our Biography field has our placeholder text, instead of a @Html helper function.



<div class="form-group">
    @Html.LabelFor(model => model.Biography, htmlAttributes: new { @class = "control-label col-md-2" })


<div class="col-md-10">
        // TODO: Place RichTextBox HTML here
        @Html.ValidationMessageFor(model => model.Biography, "", new { @class = "text-danger" })
    </div>


</div>


If you’ve found this tutorial helpful, please leave a comment below!

Send a text message using .net

Sometimes, we want to send text messages from our application.  Whether it’s for sending out a secret code for two-tiered authentication, or sending out messages to subscribed members, it’s a feature that is occasionally needed, and one that I’ve recently needed.  There are many services out there that charge a fee, either monthly or per message, but the services are out there.  I’ve also seen several open-source applications that act as an instant messaging type service, in that they have a GUI and do not provide an API.  What I was unable to find was an API that handled all carriers, and was free.  So I built one, and here is how to use it.

Your first step is to download the single DLL, which is needed to use the API.  It’s open source, so you can get the code and make any changes you want and compile the DLL yourself, if you so desire.  You can get the DLL here, and the code here.

After adding the DLL as a reference to your project, it’s actually quite simple to use.  First, add the appropriate using:

using SMSOverEmail;

After the using statement is added, we are ready to start using the API.  The first step is to instantiate a new SMSOverEmail object.

SMSOverEmail.SMSOverEmail sms = new SMSOverEmail.SMSOverEmail

With our object instantiated, we need to set up the “From” part of our text message.  We are using SMTP to send the actual text message, so this is similar to setting up a SMTP client.

sms.EnableSsl = true;
sms.SMTPUserName = "userName@gmail.com";
sms.SMTPPassword = "password";
sms.Host = "smtp.gmail.com";
sms.Port = 587;
sms.From = new System.Net.Mail.MailAddress("userName@gmail.com");

These settings are correct if you are using Gmail, but you can use any email provider you wish, as long as you know the settings Smile

Once the “From” part is set up, we can set up the “To” part.  This is a text message, so we are delivering to a mobile device.  So, for the purpose of this API, you just need to know the 10 digit phone number, and the carrier it belongs to.  The API does the rest.

sms.PhoneNumber = "2345678901"; //a ten digit phone number
sms.RecipientCarrier = Carrier.SprintPCS;

The phone number is a string, and must be the 10 digit phone number belonging to the phone.  The Carrier enumerator has over 60 carriers to choose from.  In our example, we are sending a text message to (234)567-8901 which is held by a Sprint customer.

Now that the recipient and the sender are setup, we can send our text message.

sms.SendTextMessage("test message");

The SendTextMessage function has many different overloads, they are all documented inline, but you can see the full list on the projects CodePlex documentation page.

No tutorial would be complete without showing all of the code put together in a complete console application:

namespace SSMSTest
{
    using System;
    using SMSOverEmail;

    class Program
    {
        static void Main(string[] args)
        {
            SMSOverEmail.SMSOverEmail sms = new SMSOverEmail.SMSOverEmail();
            sms.EnableSsl = true;
            sms.SMTPUserName = "userName@gmail.com";
            sms.SMTPPassword = "password";
            sms.Host = "smtp.gmail.com";
            sms.Port = 587;
            sms.From = new System.Net.Mail.MailAddress("userName@gmail.com");

            sms.PhoneNumber = "2345678901"; //a ten digit phone number
            sms.RecipientCarrier = Carrier.SprintPCS;

            sms.SendTextMessage("test message");
        }
    }
}

The API also comes with a dictionary containing the actual carrier name, along with the enumerator.  For example, Carrier.ATT is AT&T.  Each enumerator is simple to understand, but I threw it in in case you needed a user-friendly list of carriers.  Assuming sms is the name of your SMSOverEmail object, then sms.CarrierNames is the dictionary.  It’s in the form of Dictionary, where Carrier is the enumerator, and string is the user-friendly name.

As always, I would love to hear from you.  Please leave your thoughts about this post below, especially if this post has helped you Smile  If you have any questions, I’ll be sure to do my best to answer those as well.

Forcing Save As in Microsoft Word

My wife came to me a few weeks ago with a problem: When she would open a word document attached to an e-mail sent to her Yahoo account, she would sometimes lose the changes made to the document when she closed it.  Even though she clicked ‘yes’ when the program asked if she wanted to save her changes, the document was being saved to a temporary internet file that she couldn’t access once the document was closed.

There are several solutions to this problem, but my wife wasn’t particularly enthused about changing her online habits.  And, why shouldn’t she expect her document to be found after hitting the save button?  Why doesn’t MS Word recognize that the open document is in Temporary Internet Files and perform a Save As instead of a Save?  Word should, but it doesn’t, so I wrote a Word Add-In to do the trick for her.

Our solution should force a Save As if the Save function is invoked, but do not interfere if the Save As is invoked…and of course, ignore any document that is not in the Temporary Internet Files location.  I knew it would be easy, but I didn’t realize just how easy it would be. 

I’m using VS 2012 these days, but I did this in VS 2010 because we use Office 2007, and VS 2012 didn’t have (or I couldn’t find) the Office 2007 Add-In templates…so, open up VS 2010 and create a new project, choose Installed Templates –>Visual C# –>Office –>2007—>Word 2007 Add-In.

image

The template opens up with two events already wired-up, the Startup and Shutdown events.  We need to add the DocumentBeforeSave event, so first create the function:

private void Application_DocumentBeforeSave(Word.Document Doc, 
                                            ref bool SaveAsUi, 
                                            ref bool Cancel)
{
}

Doc is the document that is open.  SaveAsUi will be true if the SaveAs was invoked, false if it’s just Save.  Cancel, you can set to true if you want to cancel the save event.  One thing to remember is that neither the SaveAsUi or the Cancel properties are read-only.  You can set them to true or false; however, setting SaveAsUi to true doesn’t do anything.  It’s for your informational purposes only.  So, just remember to treat SaveAsUi as read-only, and treat Cancel as write only and you won’t get frustrated or confused.

Now, we need to add the event handler to our application.  So, put this code in the ThisAddIn_Startup function:

this.Application.DocumentBeforeSave +=
    new Word.ApplicationEvents4_DocumentBeforeSaveEventHandler(
           Application_DocumentBeforeSave);

Now, remember that we need to ignore the SaveAs event, and we have this handy variable called SaveAsUi, which tells us if we in a SaveAs event.  So, let’s start with that by adding an If statement to our DocumentBeforeSave function:

if (!SaveAsUi)
{
}

If SaveAsUi is false, then we need to check our location.  We only want to intercede if we are inside the “Temporary Internet Files” folder, so place that check inside the if statement above:

if (Doc.Path.Contains("Temporary Internet Files"))
{
}

If both conditions are true, then we need to invoke the Save As dialog box.  The only problem here is that I was not able to find a way to invoke the Save As dialog box, which means we have to make our own.   If you go to File –> Save As from just about any program, you will see a drop-down box labeled File Type.  This is populated by a property called “Filter” on the SaveFileDialog control.  If you happen to look at the Filter for MS Word 2007, you will see that there are 18 items listed.  I didn’t want to type “filter += foo” 18 times, so I created a dictionary.  We, of course, also need a SaveFileDialog control.  So first add the following using statement to your class, and then add to two properties.

using System.Windows.Forms;
private Dictionary<string, string> SaveAsFilters { get; set; }
private SaveFileDialog SaveAsDialog { get; set; }

The idea is to populate the dictionary with the filters and then just loop over the dictionary to populate the dialogs filter property.  So, inside the ThisAddIn_Startup function, include the following code.

First, populate the dictionary:

this.SaveAsFilters = new Dictionary<string, string>
{
    { "Word Document", "*.docx" },
    { "Word Macro-Enabled Document", "*.docm" },
    { "Word 97-2003 Document", "*.doc" },
    { "Word Template", "*.dotx" },
    { "Word Macro-Enabled Template", "*.dotm" },
    { "Word 97-2003 Template", "*.dot" },
    { "PDF", "*.pdf" },
    { "XPS Document", "*.xps" },
    { "Single File Web Page", "*.mht;*.mhtml" },
    { "Web Page", "*.htm;*.html" },
    { "Web Page, Filtered", "*.htm;*.html" },
    { "Rich Text Format", "*.rtf" },
    { "Plain Text", "*.txt" },
    { "Word XML Document", "*.xml" },
    { "Word 2003 XML Document", "*.xml" },
    { "OpenDocument Text", "*.odt" },
    { "Works 6.0 - 9.0", "*.wps" },
    { "Works 7.0", "*.wps" }
};

As of Word 2007, this is all the filters in Words Save-As dialog.

Even though it’s just a string, I ran into some problems appending to the Filter property, so we will create a string and append to it.  We’ll also include a counter so that we can include a pipe (“|”) character in between each filter.

int x = 1;
string filterText = string.Empty;
foreach (KeyValuePair<string, string> filter in this.SaveAsFilters)
{
    filterText += filter.Key + " (" + filter.Value + ")|" + 
                                                       filter.Value;

    //add the pipe to seperate the filters
    if (x != this.SaveAsFilters.Count())
    {
        filterText += "|";
    }
    x++;
}

this.SaveAsDialog.Filter = filterText;

We want our dialog to look and feel as much like Microsoft Word as possible, so we set the following properties:

this.SaveAsDialog.Filter = filterText;
this.SaveAsDialog.Title = "Save As";
this.SaveAsDialog.SupportMultiDottedExtensions = true;
this.SaveAsDialog.OverwritePrompt = true;
this.SaveAsDialog.InitialDirectory = Environment.GetFolderPath(
                              Environment.SpecialFolder.MyDocuments);
this.SaveAsDialog.AutoUpgradeEnabled = true;
this.SaveAsDialog.AddExtension = true;
this.SaveAsDialog.DefaultExt = "docx";

Now that we have everything in place, we can finish off our DocumentBeforeSave event by including the following code inside our nested if statement:

if (this.SaveAsDialog.ShowDialog() == DialogResult.OK)
{
    Doc.SaveAs(this.SaveAsDialog.FileName);
}

Only act if the user presses the “Save” button, and when they do call the Documents SaveAs method, passing in the filename and path that the user selected.  This will save the document, but it still doesn’t feel just right.  If I call SaveAs from any program (MS Word included), the current document is changed to reflect the newly created document.  Calling the SaveAs function in this manner does not do that, so we need some code to mimic that behavior.

Cancel = true;
this.Application.Documents.Close();
this.Application.Documents.Open(this.SaveAsDialog.FileName);

First we cancel the calling save event, then we can close the current document and then open the new document.

And that’s it, now we have created our Word Add-In.  Before I show you the code in it’s entirety, we need to walk through the publish process.  Word Add-Ins are published using ClickOnce, and because it’s integrating into Word, it requires a certification to go along with it.  I didn’t want to spend the money on a Signed Cert., so I published it without one.  It’s easy to do if you can control the computer you are installing to.

First thing is to set up ClickOnce.  Right-click on your project file in Soultion Explorer and choose Properties, and then click on the Publish Tab:

image

The first box is where the deployment files will be uploaded to.  I use ftp because I’m sending this to my website, but you could also choose to use a UNC path.  The nested box below that is where to go to download the installation files.  So, I upload to an ftp site, and then point to a webpage for the actual deployment.  In my case it’s http://www.karrapps.com/tempsaveintercept/TempSaveIntercept.vsto.

By default, the prerequisite options are set to allow the program to download and install any missing prereqs from each vendor’s web site.  I recommend leaving it this way.  If you need to change this behavior though, just click the Prerequisites button.

Your add-in will check for updates “every time the customization runs” by default.  For my purposes this isn’t really that important, so I clicked the Update button and set it to check every 1 day.  You can also select to never check for updates.

The Options buttons contains two sections; Description and Office Settings.  Here you can select a publishing language, define the publishers author, define the product name, and provide a support url.  Office settings include Solution name, and description as well as a choice between loading the Add-In at startup or on demand.

Once the settings are to your liking, just click the Publish Now button, address any prompts that come up (authentication prompts for example), and your ready for the client PC.

Here’s where we get around the cert.  This method will still prompt the user with a warning that the installation can’t be trusted, but it works and is free.  The website domain will need to be added as a trusted location, you can do that under Internet Options –> Security—>Trusted Sites—>Sites button.  So, I add http://karrapps.com as a trusted site.

Then, download and run setup.exe from the Installation Folder and that’s it.  The Add-In is automatically installed and enabled inside MS Word.

Now, the code in it’s entirety:

namespace TempSaveIntercept
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Forms;
    using Word = Microsoft.Office.Interop.Word;

    public partial class ThisAddIn
    {
        private Dictionary<string, string> SaveAsFilters;
        private SaveFileDialog SaveAsDialog = new SaveFileDialog();

        private void ThisAddIn_Startup(object sender, 
                                       System.EventArgs e)
        {
            this.Application.DocumentBeforeSave +=
         new Word.ApplicationEvents4_DocumentBeforeSaveEventHandler(
                                    Application_DocumentBeforeSave);

            this.SaveAsFilters = new Dictionary<string, string>
            {
                { "Word Document", "*.docx" },
                { "Word Macro-Enabled Document", "*.docm" },
                { "Word 97-2003 Document", "*.doc" },
                { "Word Template", "*.dotx" },
                { "Word Macro-Enabled Template", "*.dotm" },
                { "Word 97-2003 Template", "*.dot" },
                { "PDF", "*.pdf" },
                { "XPS Document", "*.xps" },
                { "Single File Web Page", "*.mht;*.mhtml" },
                { "Web Page", "*.htm;*.html" },
                { "Web Page, Filtered", "*.htm;*.html" },
                { "Rich Text Format", "*.rtf" },
                { "Plain Text", "*.txt" },
                { "Word XML Document", "*.xml" },
                { "Word 2003 XML Document", "*.xml" },
                { "OpenDocument Text", "*.odt" },
                { "Works 6.0 - 9.0", "*.wps" },
                { "Works 7.0", "*.wps" }
            };

            int x = 1;
            string filterText = string.Empty;
            foreach (KeyValuePair<string, string> filter in 
                                                 this.SaveAsFilters)
            {
                filterText += filter.Key + " (" + filter.Value + 
                                                 ")|" + filter.Value;

                //add the pipe to seperate the filters
                if (x != this.SaveAsFilters.Count())
                {
                    filterText += "|";
                }
                x++;
            }

            this.SaveAsDialog.Filter = filterText;
            this.SaveAsDialog.Title = "Save As";
            this.SaveAsDialog.SupportMultiDottedExtensions = true;
            this.SaveAsDialog.OverwritePrompt = true;
            this.SaveAsDialog.InitialDirectory = 
    Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            this.SaveAsDialog.AutoUpgradeEnabled = true;
            this.SaveAsDialog.AddExtension = true;
            this.SaveAsDialog.DefaultExt = "docx";
        }

        private void ThisAddIn_Shutdown(object sender, 
                                        System.EventArgs e)
        {
        }

        private void Application_DocumentBeforeSave(Word.Document Doc,
                                                    ref bool SaveAsUi,
                                                    ref bool Cancel)
        {
            if (!SaveAsUi)
            {
                if (Doc.Path.Contains("Temporary Internet Files"))
                {
                    if (this.SaveAsDialog.ShowDialog() == 
                                                     DialogResult.OK)
                    {
                        Doc.SaveAs(this.SaveAsDialog.FileName);
                        Cancel = true;
                        this.Application.Documents.Close();
                        this.Application.Documents.Open(
                                         this.SaveAsDialog.FileName);
                    }
                }
            }
        }

        #region VSTO generated code
                …………………………
        #endregion
    }
}

Please note that I had to adjust the spacing to make everything fit on the page, but you get the idea Winking smile

Please leave your comments below.  I would love to hear from you.

Remove Recipients From Email When Not In Release Mode

Often, we need our applications to send emails to people.  When this is the case, if we are not careful, we can end up sending test emails to our external users.  We can get around this very easily by using preprocessor directives and writing our own SmtpClient class.

This isn’t a tutorial on sending email through .net, but here is a very basic function that will do that.  First the usings:

using System.Collections.Generic;
using System.Net.Mail;

Then the function:

public void sendEmail(string message, string subject, MailAddress fromAddress, 
            List<MailAddress> toAddressList, List<MailAddress> ccAddressList, 
            List<MailAddress> bccAddressList)
{
    MailMessage mail = new MailMessage();
    SmtpClient client = new SmtpClient();

    mail.From = fromAddress;
    mail.Subject = subject;
    mail.Body = message;

    foreach (MailAddress address in toAddressList)
    {
        mail.To.Add(address);
    }

    foreach (MailAddress address in ccAddressList)
    {
        mail.CC.Add(address);
    }

    foreach (MailAddress address in bccAddressList)
    {
        mail.Bcc.Add(address);
    }

    client.Host = "smtp.gmail.com";
    client.Port = 587;
    client.Credentials = new System.Net.NetworkCredential("username", "****");
    client.EnableSsl = true;

    client.Send(mail);
}

Now, that is just a generic function that will send an email.  I’m not going to bother explaining it.  See that last line though: “client.Send(mail);”?  That Send function is what we want to change.  To do that, we create a new class, with SmtpClient as the base, and then overload the Send function.

Our usings are the same:

using System.Collections.Generic;
using System.Net.Mail;

And here is our new class:

class NewSmtpClient : SmtpClient
{
    public void Send(MailMessage message)
    {
#if DEBUG
        List<MailAddress> toList = new List<MailAddress>(message.To);
        List<MailAddress> ccList = new List<MailAddress>(message.CC);
        List<MailAddress> bccList = new List<MailAddress>(message.Bcc);

        foreach(MailAddress address in toList)
        {
            if(address.Host.ToLower() != "gmail.com")
            {
                message.To.Remove(address);
            }
        }

        foreach (MailAddress address in ccList)
        {
            if (address.Host.ToLower() != "gmail.com")
            {
                message.CC.Remove(address);
            }
        }

        foreach (MailAddress address in bccList)
        {
            if (address.Host.ToLower() != "gmail.com")
            {
                message.Bcc.Remove(address);
            }
        }
#endif

        //there must be at least one recipient 
        if ((message.To.Count + message.CC.Count + message.Bcc.Count) > 0)
        {
            base.Send(message);
        }
    }
}

Of course, now that we have our new class, we need to change our calling function.  So, in our generic function we will change

SmtpClient client = new SmtpClient();

to

NewSmtpClient client = new NewSmtpClient();

So, I want to explain a few things here.  See that fifth line, #if DEBUG?  That’s a preprocessor directive.  Visual Basic has them to, but MSDN just called them directives.  Anyway, these directives are processed before the compiler runs.  Anything between #if DEBUG and #endif will not even show up in the IL Code if you compile in Release mode.

What is happening in our example class here, is that when you call client.Send(mail), if you are in DEBUG mode, then we remove any email recipient whose host is not “gmail.com”.  Of course, you would use any host that you want.  This results in a recipient list that is any of the original recipients but, no emails will be sent to “gmail.com”.  I don’t think that is worded very well, so let me visualize it for you.

  • To List
    • one@gmail.com
    • one@yahoo.com
    • one@bing.com
  • CC List
    • two@gmail.com
    • two@yahoo.com
    • two@bing.com
  • BCC List
    • three@gmail.com
    • three@yahoo.com
    • three@bing.com

After we call Send, but before the email is actually sent, the new recipient list looks like this:

  • To List
    • one@gmail.com
  • CC List
    • two@gmail.com
  • BCC List
    • three@gmail.com

The if statement at the end is because it is possible that we will end up removing all of our recipients, in which case we don’t want to send the email.  In this scenario, but without such an if statement, an exception will be thrown.

If this post has helped you in any way, please leave a comment below. Or, if you solved this method another way, please leave a comment.  I would like to hear from you!

Crystal Reports and Visual Studio 2010

Visual Studio(VS) editions older than VS2010 had Crystal Reports(CR) included out of the box. Unfortunately, the relationship between Microsoft and SAP ended with Visual Studio 2010 so when you open it up you will notice that there is not any CR tools.

Even though CR is not included with VS, you can still download CR for VS2010, for free, as a separate install. You can find the download here: http://www.businessobjects.com/jump/xi/crvs2010/us2_default.asp

You will want to use the first link: SAP Crystal Reports, version for Visual Studio 2010 – Standard (MAKE SURE YOU HAVE VS2010 INSTALLED FIRST!)

Now, any project that you compile inside VS2010 that uses CR will use the new version of Crystal, even if the project was originally built in VS2008 or prior. This means that our customers will be required to have SAP Crystal Reports runtime engine for .NET Framework 4 installed on their computers. This is true no matter what .NET Framework you use to build your project. The 32bit and 64bit client runtimes are available at the link provided above.

***NOTE*** It will be understood why in a minute, but we will call the new version of CR “Version 13” and the old version “Version 10”.

Version 13 and Version 10 do install nicely side-by-side, and they play nice together also. The big problem here is that Version 13 does require an administrator to physically log onto Windows to install (I can’t remember which, but between Windows XP and Windows 7 one required the admin to log-on and the other allowed the admin to right-click and choose “Run As”. Both OS’s give the option, but it only works on one.) Additionally the install takes well over 30 minutes to complete and as far as I know there is not a silent install available.

If you don’t need any of the features of Version 13, you can still use Version 10; here’s how.

If you are not using the Crystal Report Viewer control (for example, you are just using the ReportDocument class in your code) then you can just reference the Version 10 Dlls. Instructions follow for both Visual Basic and C#:

  • Visual Basic
    1. Right-click on your project file and go to properties
    2. Go to the Reference Tab
    3. Make a note of all references listed that begin with CrystalDecisions and are of Version 13 and higher
    4. Select the CrystalDecisions with a version of 13 and higher and click the Remove button
    5. Click the Add Button
    6. Select the .Net Tab
    7. Select the Version 10 versions of the CrystalDecisions dlls that you need
    8. Click OK
  • C#
    1. In solution explorer expand the References folder
    2. Make a note of all references listed that begin with CrystalDecisions and are of Version 13 and higher
    3. Select and delete the CrystalDecisions with a version of 13 and higher
    4. Right-Click the References folder and select Add Reference
    5. Select the .Net Tab
    6. Select the Version 10 versions of the CrystalDecisions dlls that you need
    7. Click OK

Now, if you do want to use the Crystal Report Viewer you can still use the old dlls. Here is how:

  1. Add the Crystal Report Viewer control to your web form
  2. Open the designer file for your form
  3. Comment out the following lines in C# or VB respectfully
    • C#
      1. this.CrystalReportViewer1.EnableRefresh = false;
      2. this.CrystalReportViewer1.ReuseParameterValuesOnRefresh = true;
      3. this.CrystalReportViewer1.ToolPanelView = CrystalDecisions.Windows.Forms.ToolPanelViewType.None;
    • VB
      1. Me.CrystalReportViewer1.EnableRefresh = False
      2. Me.CrystalReportViewer1.ReuseParameterValuesOnRefresh = True
      3. Me.CrystalReportViewer1.ToolPanelView = CrystalDecisions.Windows.Forms.ToolPanelViewType.None

If your project is using ClickOnce then don’t forget to remove the prerequisite for “SAP Crystal Reports Runtime Engine for .NET Framework 4.0”

***NOTE*** during my testing and preparation of this tutorial, I found that after I told VS2010 to use the Version 10 dlls for one project, it sometimes used them for new projects by default.

When opening your project again VS2010 wants to be helpful and upgrade CR for you.

When this happens, just choose Open the solution without conversion and press OK:

And that’s it, now you are using Crystal Reports with Visual Studio 2010!

Only parameterless constructors and initializers are supported in LINQ to Entities

Want to populate a Dictionary of type long, Tuple using LINQ to Entities? Well you can’t.  But you can use LINQ to Entities to return an object and then use LINQ to Objects to populate your dictionary.

Dictionary<long, Tuple<string, string>> pages = 
                               new Dictionary<long, Tuple<string, string>>();

To populate, you would expect the following to work:

pages = (from n in dbContext.Navigations
         select new
         {
             Key = n.PageId,
             Value = new Tuple<string, string>
             (n.Page,
             n.Link)
         }).ToDictionary(n => n.Key, n => n.value);

But, of course I’m using LINQ to Entities so at run-time the compiler says “Only parameterless constructors and initializers are supported in LINQ to Entities”! In LINQ to SQL you are allowed to do this, but not LINQ to Entities, so is this a bug? Nope, Microsoft has done this by design. From what I’ve read it basically boils down to this: LINQ allows you to combine server (SQL) code and client (.NET) code in your query and during execution LINQ will try to figure out what is what. LINQ to Entities forces separation of concerns here and so you cannot mix .NET and SQL code together.

So, if we can’t combine the two together (like in our example above), how do we populate our dictionary? Well, there are two ways. The first step is the same no matter which way you go, get the data from the server. From there you can iterate through the data through a foreach loop and populate the dictionary that way, or you can use LINQ to populate the dictionary. Everyone knows how to use a foreach loop, so I’ll just show you the LINQ method.

First, retrieve the data from the server:

var fromServer = (from n in dbContext.Navigations
                  select new
                  {
                      Id = n.PageId,
                      Page = n.Page,
                      Link = n.Link
                  }).ToList();

Then, use LINQ to populate the dictionary:

pages = (from n in fromServer
         select new
         {
             Key = n.Id,
             Value = new Tuple<string, string>
             (n.Page,
              n.Link)
         }).ToDictionary(n => n.Key, n => n.Value);

You might note that this LINQ statement looks very familiar to the initial LINQ query, but remember, the first didn’t work because LINQ to Entities does not know how to instantiate (in this case) a new Tuple on SQL Server. The LINQ statement above works because LINQ to Objects runs on the client and therefore knows about Tuples.

Easy Tear Off Tool Strip

About the same time I saw this how-to from Microsoft, I was starting a project that had a need for a “tear-off tool strip.” When I got to the part of my project to implement this, I went back to the article, and decided it was way too complicated for what I needed. I just needed to be able to move a given toolbox outside of the parent form. So, I did it myself and made it much, much easier.

For this example, we will pretend we are making a text editor with the editor tools as a tool box that can be torn out of the parent form. So, I made a simple windows form and added a tool-strip with a button on it, and a text box.

Next, I created a second windows form to use as our tool box.

Be sure that you set the FormBorderStyle to FixedToolWindow.
Now for the code. In the parent form, we need to set a variable for the loaded width of the form:

public int FormWidth { get; set; }

Be sure to set the variable to public so the tool box can access it. We set the variable in the forms load event:

public void Form1_Load(object sender, EventArgs e)
{
    this.FormWidth = this.Width;
}

Next, add the click event for the ToolBox button:

public void toolBoxToolStripMenuItem_Click(object sender, EventArgs e)
{
    ToolBox box = new ToolBox();
    //Toplevel controls cannot be added to a form, so set TopLevel to false
    box.TopLevel = false;
    this.Controls.Add(box); 

    //Make Form1 wider to house the ToolBox.
    this.Width += box.Width + 50;

    //Set the location of the box
    box.Location = new Point(this.FormWidth + 25, 25);

    //Set the ToolBar flag to true
    box.ToolBar = true;

    //Show the box.
    box.Visible = true;
}

That’s all the code we need for our parent form. In our tool box we need a public accessible flag and a move event handler:

public bool ToolBar { get; set; } 

public void ToolBox_Move(object sender, EventArgs e)
{
    if (this.ToolBar)
    {
        this.ToolBar = false;
        Form1 parent = (Form1).ParentForm;
        parent.Width = parent.FormWidth;
        this.DesktopLocation = MousePosition;
        parent.Controls.Remove(this);
        this.TopLevel = true;
    }
}

And that’s all there is to it.

When we click the ToolBar button the window will expand and the tool box is loaded inside the parent. If we try to move the ToolBox it will “tear off” the parent form.