Friday, July 15, 2011

Easily Disable Row Selection for a Silverlight DataGrid

This technique actually prevents the row from being selected and prevents an unwanted cell from being selected. The visual effect is that the row or cell in that row never appears to be selected.

I assume you know how to load you DataGrid with data, etc. In my example, I only have one column of data. The trick to this solution is almost all in code behind. Also, this all assumes that you have set IsReadOnly="True" either in the XAML or the code behind.

 

In this example my DataGrid is named dgAnnouncements. I handle the SelectionChanged event as show in the method below.

private void dgAnnouncements_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    dgAnnouncements.SelectedItem = null;
}

The above gets us most of the way there, however you will notice that the cell itself can still be selected. To handle this we have to do a trick.

In the XAML for the DataGrid you will need to add an invisible column at position 0 so that we can select it whenever someone tries to select another cell that they can see.

Here is an example of the XAML for the DataGrid.

<sdk:DataGrid x:Name="dgAnnouncements"
ItemsSource="{Binding Data, ElementName=dsAnnouncements}"
AutoGenerateColumns="False"
IsReadOnly="True"
CurrentCellChanged="DataGrid_CurrentCellChanged"
SelectionChanged="dgAnnouncements_SelectionChanged">

    <sdk:DataGrid.Columns>
        <sdk:DataGridTextColumn Binding="{Binding Path=Nothing}" MinWidth="0" MaxWidth="0" />
        <sdk:DataGridTextColumn Binding="{Binding Path=Title}"  />
    </sdk:DataGrid.Columns>
</sdk:DataGrid>

Here is the code behind to redirect the current cell to our invisible cell.

private void DataGrid_CurrentCellChanged(object sender, EventArgs e)
{
    if (dgAnnouncements.CurrentColumn != null)
    {
        dgAnnouncements.CurrentColumn = dgAnnouncements.Columns[0];
    }
}

Thursday, July 14, 2011

Creating your own MessageBox in Silverlight

The MessageBox built into Silverlight is nice enough to use for simple OK, Cancel scenarios, but sometimes I want to change the text of the buttons in the MessageBox. For example, I may want Yes, No instead of OK, Cancel or maybe completely different text like Finished Now, Finish Later. The syntax of the MessageBox is simple enough so I decided to mirror it as closely as I could. This makes it as painless as possible to change from a MessageBox to my message box. The biggest difference in usage that I couldn’t figure out how to get around is that the MessageBox is a blocking call, but the UI thread still continues to render. When I tried to do the same, the UI thread did not continue to render. I looked at the code for MessageBox and it appears to be calling a Windows native MessageBox that is most likely different for OS that Silverlight is implemented on. So, I decided that in the Silverlight manor I would use asynchronous calls (event handlers) instead of a blocking call. When I use delegates the code is similar to using MessageBox.

Okay, enough description I think. Here is source code for my version of MessageBox called MessageBox2

Download Source Code for Control

MessageBox2.xaml.cs   MessageBox2.xaml

Usage

You’ll notice I have 3 Show() methods instead of the standard 2 that MessageBox has. The reason is that I added one so that you can specify the text of OK and Cancel buttons. You’ll notice that I don’t return a MessageBoxResult and instead return the MessageBox itself.  Below is how you would use the MessageBox versus MessageBox2.

MessageBox

MessageBoxResult result = MessageBox.Show("This is a choice test", "Some caption", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
    DoSomethingUsefulHere();
}
else if (result == MessageBoxResult.Cancel)
{
    DoSomethingUsefulHere();
}

MessageBox2

var msgBox2 = MessageBox2.Show("This is a choice test", "Some caption", MessageBoxButton.OKCancel, "Yes", "No");
msgBox2.OKButton.Click += delegate(object sender2, RoutedEventArgs e2) { DoSomethingUsefulHere(); };
msgBox2.CancelButton.Click += delegate(object sender2, RoutedEventArgs e2) { DoSomethingUsefulHere(); };

You could also use the Closed event and check the DialogResult to see if it was accepted or cancelled. Also, notice I changed the OK button to Yes and the Cancel button to No.

Tuesday, June 28, 2011

Reliable Sending of Email for.Net/C# using SQL Server

If you want more reliable sending of emails from your .Net code you may think it is too much work to implement logging, retrying if smtp server is unavailable, control returned to user while email is sending, etc. The truth is that if you try to do it all via ASP.NET this can involve a lot of work. Thankfully, there is an easy way to meet all these requirements and not have to do much work at all.

As it turns out SQL Server has a robust facility for sending emails called Database Mail. It meets all the above requirements and these requirements are configurable. I have posted many entries on Database Mail such as resending emails, reviewing logs, etc. Just search on my blog for them. Much of the configuration comes from the Profile and Account settings you configure when you setup this info for your app.

Below is a C# method that you can add to your project. Calling this will allow you to send email reliably from your app and not have to worry about the delivery of it. it assumes that you have a class called Config with two static methods YourConnectionString and AllowEmailsToBeSent. You can hard code those obviously. The two methods just pull the values from the Web.config, but it could be anywhere in theory. The first is just your database connection string and the second controls whether the contents of the email will be written to a hard coded path on your harddrive for testing or it will be connect to SQL Server and actually send the email. This is useful for development / testing when you don’t want to disturb real users.

/// <summary>
/// Calls SQL Server Send Mail procedure, or logs to log file
/// </summary>
/// <param name="fromEmailAddress"></param>
/// <param name="toEmailAddresses">semi-colon separated list of recipients</param>
/// <param name="subject"></param>
/// <param name="body"></param>
public void SendEmail(string fromEmailAddress, string toEmailAddresses, string subject, string body)
{
    // this should never happen, but just so I will know about it
    if (string.IsNullOrEmpty(toEmailAddresses))
    {
        toEmailAddresses = "test@yourdomain.com";
    }
    // this should never happen, but just so I will know about it
    if (string.IsNullOrEmpty(fromEmailAddress))
    {
        fromEmailAddress = "test@yourdomain.com";
    }


    if (Config.AllowEmailsToBeSent)
    {
        // send email here
        using (SqlConnection conn = new SqlConnection(Config.YourConnectionString))
        {
            conn.Open();

            // use sql server database mail instead of .net mail
            // so that we get retry, logging, automatic batching, etc for free. Better reliability and recover.
            //-- for all parameters:
http://msdn2.microsoft.com/en-us/library/ms190307.aspx   
            SqlCommand cmd = new SqlCommand("msdb.dbo.sp_send_dbmail", conn);
            cmd.CommandType = CommandType.StoredProcedure;

            cmd.Parameters.Add("@profile_name", SqlDbType.VarChar).Value = "Your Profile name here";
            cmd.Parameters.Add("@recipients", SqlDbType.VarChar).Value = toEmailAddresses;
            cmd.Parameters.Add("@subject", SqlDbType.VarChar).Value = subject;
            cmd.Parameters.Add("@body", SqlDbType.VarChar).Value = body;

            cmd.ExecuteNonQuery();
        }

    }
    else
    {

        StreamWriter writer = new StreamWriter(@"c:\Email.log", true);
        using (writer)
        {
            writer.WriteLine("\r\n\r\nDate/Time Stamp: " + DateTime.Now.ToLocalTime());
            writer.WriteLine("From: " + fromEmailAddress);
            writer.WriteLine("To: " + toEmailAddresses);
            writer.WriteLine("Subject: " + subject);
            writer.WriteLine("Body: " + body);
        }
    }
}

Monday, June 27, 2011

Show JavaScript alert() or execute other code from Code-Behind

I find that sometimes I need to show the user a JavaScript alert for something I don’t want them to be able to miss and I only want to show it based on some server-side (Code-Behind) code. This will work for most any chunk of JavaScript code as well.

Below is the code that you can put in your Page_Load()

if (!Page.ClientScript.IsClientScriptBlockRegistered("MyMadeUpNameHere"))
{
    string jscript = "alert('test here');";
    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "MyMadeUpNameHere", jscript, true);
}

Notice that MyMadeUpNameHere is some unique string that allows you to not have multiple copies of your code on the page. This ensures that you will only have it execute once. If you don’t want that functionality then don’t do the check.

In this example, the user will see the ‘test here’ in a JavaScript alert (Message Box).

Note, that the RegisterClientScriptBlock puts the JavaScript immediately below the opening form tag. If your code references any form elements or any other elements on the page this is most likely going to be too early since the page has not finished loading. This makes this method good for simple things like I did here that doesn’t really care about the page, or you can use it to register JavaScript functions that is executed later in the page or in an event handler.

If you want your code to execute LATER then I recommend using the RegisterStartupScript and IsStartUpScriptRegistered methods instead. This code executes at the end of the page after all the items on the page have finished loading. Here is the same code as above, but with the StartUp versions.

if (!Page.ClientScript.IsStartupScriptRegistered("MyMadeUpNameHere"))
{
    string jscript = "alert('test here');";
    Page.ClientScript.RegisterStartupScript(this.GetType(), "MyMadeUpNameHere", jscript, true);
}

Like I said before, if you had more complex logic you could put it earlier in the page and just execute the function using the RegisterStartupScript. You can also put your complex logic in functions in JScript include and use the RegisterClientScriptInclude. See here for more details on that.

I don’t know why I can never remember which methods to use for showing a JavaScript alert or executing other JavaScript code when the page loads, so I am writing in down for me and everyone else to have.

Tuesday, June 21, 2011

Get usage for all columns in a SQL Server database

First if you just want to know how one column is used you want to use the UI or check out my other entry. If however you want to scan your entire database and determine the dependencies (usage) of each and every column in the database then stay here. The code found in either of these places will only look at the database references (stored procedures, functions, views, triggers). This means if you are using something like SSIS, LINQ, Entity Framework, embedded SQL in your code, etc you will need to check these area on your own.

With that said, I really just continued the thought from my other entry (same as the one I noted above). The first step is to convert the stored procedure used there to one that just dumps the data to a table for later querying. I call this table ColumnUsage. I also add a TagName to the table so that you can easily identify your results among other results in that table. That way you can compare results over time, or just support multiple users. The table will be created the first time the stored procedure is executed.

Here is the new stored proc

/****** Object:  StoredProcedure [dbo].[UTL_07_WriteColumnUsageToColumnUsageTable]    Script Date: 06/21/2011 15:25:47 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

alter PROCEDURE [dbo].[UTL_07_WriteColumnUsageToColumnUsageTable]
    @vcTableName varchar(100),
    @vcColumnName varchar(100),
    @tagName varchar(100)
AS
/************************************************************************************************
DESCRIPTION:    writes all stored procedures, views, triggers
        and user-defined functions that reference the
        table/column passed into this proc into the ColumnUsage table.
   
PARAMETERS:
        @vcTableName - table containing searched column
        @vcColumnName    - column being searched for
        @tagName - a name you make up to so you can later query for your results

        This procedure must be installed in the database where it will
        be run due to it's use of database system tables.

USAGE:   
  
  UTL_07_WriteColumnUsageToColumnUsageTable 'schema.tablename', 'columnname', 'tagName'
   
AUTHOR:    Karen Gayda

DATE: 07/19/2007

MODIFICATION HISTORY:
WHO        DATE        DESCRIPTION
---        ----------    -------------------------------------------
Brent Vermilion    06.21.2011    Recreated such that writes to a table instead of output
*************************************************************************************************/
SET NOCOUNT ON

IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND  TABLE_NAME = 'ColumnUsage'))
BEGIN
    CREATE TABLE dbo.ColumnUsage
        (
        ID int NOT NULL IDENTITY (1, 1),
        TableName varchar(500) NOT NULL,
        ColumnName varchar(500) NOT NULL,
        UsedByType varchar(50) NOT NULL,
        UsedByName varchar(2000) NOT NULL,
        TagName varchar(100) NOT NULL
        )  ON [PRIMARY]

    ALTER TABLE dbo.ColumnUsage ADD CONSTRAINT
        PK_ColumnUsage PRIMARY KEY CLUSTERED
        (
        ID
        ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

END

insert into dbo.ColumnUsage(TableName, ColumnName, UsedByType, TagName, UsedByName)
SELECT DISTINCT @vcTableName, @vcColumnName, 'Stored Procedure', @tagName, SUBSTRING(o.NAME,1,60) AS [Procedure Name]
        FROM sysobjects o
        INNER JOIN syscomments c
            ON o.ID = c.ID
        WHERE     o.XTYPE = 'P'
            AND c.Text LIKE '%' + @vcColumnName + '%' + @vcTableName + '%' 
    ORDER BY  [Procedure Name]


insert into dbo.ColumnUsage(TableName, ColumnName, UsedByType, TagName, UsedByName)
SELECT DISTINCT @vcTableName, @vcColumnName, 'View', @tagName, SUBSTRING(o.NAME,1,60) AS [View Name]
        FROM sysobjects o
        INNER JOIN syscomments c
            ON o.ID = c.ID
        WHERE     o.XTYPE = 'V'
            AND c.Text LIKE '%' + @vcColumnName + '%' + @vcTableName + '%'             
    ORDER BY  [View Name]


insert into dbo.ColumnUsage(TableName, ColumnName, TagName, UsedByName, UsedByType)
SELECT DISTINCT @vcTableName, @vcColumnName, @tagName, SUBSTRING(o.NAME,1,60) AS [Function Name],
        CASE WHEN o.XTYPE = 'FN' THEN 'Scalar Function'
            WHEN o.XTYPE = 'IF' THEN 'Inline Function'
            WHEN o.XTYPE = 'TF' THEN 'Table Function'
            ELSE '? Function'
        END
        as [Function Type]
        FROM sysobjects o
        INNER JOIN syscomments c
            ON o.ID = c.ID
        WHERE     o.XTYPE IN ('FN','IF','TF')
            AND c.Text LIKE '%' + @vcColumnName + '%' + @vcTableName + '%' 
    ORDER BY  [Function Name]


insert into dbo.ColumnUsage(TableName, ColumnName, UsedByType, TagName, UsedByName)
SELECT DISTINCT @vcTableName, @vcColumnName, 'Trigger', @tagName, SUBSTRING(o.NAME,1,60) AS [Trigger Name]
        FROM sysobjects o
        INNER JOIN syscomments c
            ON o.ID = c.ID
        WHERE     o.XTYPE = 'TR'
            AND c.Text LIKE '%' + @vcColumnName + '%' + @vcTableName + '%'     
    ORDER BY  [Trigger Name]

Now that we have that we can write another stored proc that will loop over every column in our database and call the above stored procedure for each column. Please note, depending on how complex your schema, the number of stored procedures, number of columns, etc this query could take quite a long time. Here is the stored proc to scan your database.


/****** Object:  StoredProcedure [dbo].[UTL_08_WriteAllColumnUsageToColumnUsageTable]    Script Date: 06/21/2011 15:25:47 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

alter PROCEDURE [dbo].[UTL_08_WriteAllColumnUsageToColumnUsageTable]
    @TagName varchar(100)
AS
/************************************************************************************************
DESCRIPTION:    writes all stored procedures, views, triggers
        and user-defined functions that reference the
        table/column passed into this proc into the ColumnUsage table.
   
PARAMETERS:
        @TagName - a name you make up to so you can later query for your results

        This procedure must be installed in the database where it will
        be run due to it's use of database system tables.

USAGE:   
  
  UTL_08_WriteAllColumnUsageToColumnUsageTable 'tagName'
   
AUTHOR:    Brent Vermilion

DATE: 06/21/2011
*************************************************************************************************/

Declare @TableName as nvarchar(512)
Declare @ColumnName as nvarchar(300)

Declare ColumnCursor CURSOR FAST_FORWARD FOR
SELECT TABLE_SCHEMA + '.' + TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS order by TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION, COLUMN_NAME

OPEN ColumnCursor
FETCH NEXT FROM ColumnCursor
into @TableName, @ColumnName

WHILE @@FETCH_STATUS = 0
BEGIN
    -- do row specific stuff here
    exec UTL_07_WriteColumnUsageToColumnUsageTable @TableName, @ColumnName, @TagName

    FETCH NEXT FROM ColumnCursor
    into @TableName, @ColumnName
END

CLOSE ColumnCursor
DEALLOCATE ColumnCursor

After executing the either of the stored procedure on your database you can answer some very useful questions.

  1. What columns are not being used?
  2. select distinct allcols.TABLE_SCHEMA + '.' + allcols.TABLE_NAME, COLUMN_NAME, UsedByName from
    INFORMATION_SCHEMA.COLUMNS allcols
    left outer join ColumnUsage usage
    on (allcols.TABLE_SCHEMA + '.' + allcols.TABLE_NAME = usage.TableName and allcols.COLUMN_NAME = usage.ColumnName)
    where UsedByName is null

  3. What is using a particular column?
    select * from ColumnUsage where TableName = 'MyTableHere' and ColumnName = 'MyColumnHere'

Friday, June 17, 2011

Deploying RIA Services seems a bit too complicated

I have spent many hours troubleshooting deployments my Silverlight application (server side) just to figure out that I have the wrong version of a .dll. It seems that the easiest way to guarantee that you have the correct assemblies is to open your web project and select all the References and change the Copy Local property to True. I didn’t notice more than a second or two delay in build time and then when I deploy I know that I will get all the assemblies I need.

Generally, you don’t need to worry about the references changing except when you get a service pack, a new version, upgrade, etc so if you like you can keep the Copy Local set to False most of the time. Then when your references change be sure to change the Copy Local back to True.

I’m so lost as to why this is so difficult.

If anyone has a better way, please let me know. What price can you put on sanity? Smile

Wednesday, June 15, 2011

Renaming a Visual Studio Project

I had a project I wanted to rename from ProjectA to ProjectB. I wasn’t quite sure how to do this. I mean, I wanted the namespaces to change, the default namespaced (used for new files), project file names, filenames, etc. In my case, I had multiple projects in my solution. I wasn’t sure how this would work.

In the end, I used a search and replace tool to change every reference in every fiel from ProjectA to ProjectB. Then I renamed all the files. To find the files that need to be renamed, you might want to check here. Then I opened the solution up in Visual Studio 2010 (VS2010) and it just worked! So cool! Thanks to most files being text or XML this worked seamlessly.