Wednesday, June 17, 2009

I will be speaking at the Central Ohio SharePoint User Group tomorrow from 5:30-7:00 PM.  The topic will be SharePoint Security.  Stop by if you are in the area and interested.

Tuesday, May 19, 2009

This might seem obvious, but web parts can easily be embedded within other web parts.  The abstract web part class inherits from the System.Web.UI.WebControls.WebControl (Actually it inherits from System.Web.UI.WebControls.WebParts.Part which inherits from System.Web.UI.WebControls.Panel which inherits from System.Web.UI.WebControls.WebControl).  This allows web parts to be added to the control tree the way any other control is added.

this.Controls.Add(new YourCustomWebPartClass());

Thursday, January 29, 2009

I recently wrote a SharePoint web part where a requirement was to populate a drop down list with all sites (SPWebs) of a site collection.  This is probably a very common scenario but the API didn’t support it easily.  Initially it looked very straightforward.  The site collection property exposes an AllWebs property which is an SPWebCollection of every site within the site collection.  Unfortunately, this will cause an Access Denied exception when accessed by any user not in the site collection administrators group, even if that user has access to every site in the collection.

Another approach was to use the GetSubWebsForCurrentUser method on the top level site object.  Unfortunately, this method does not retrieve the entire site tree, but only the child sites directly underneath the parent site.  The below function will recursively call the GetSubWebsForcurrentUser method for each sub site and bind the results to a drop down list.

private void RecursivelyPopulateDDLSites(DropDownList DDLSites, SPWebCollection Webs)
        {
            foreach (SPWeb web in Webs)
            {
                
               DDLSites.Items.Add(new ListItem(web.Title, web.ID.ToString()));
               if (web.GetSubwebsForCurrentUser().Count > 0)
               {
               RecursivelyPopulateDDLSites(DDLSites, web.GetSubwebsForCurrentUser());
               }

            }
            
        }

Wednesday, January 28, 2009

The .net framework contains useful classes for encryption found in the System.Security.Cryptography namespace. Even though these classes shield the developer from the complexity of the encryption algorithms, there is still a significant amount of implementation detail to handle.  Under most scenarios, a developer simply wants a function that takes a clear text string as a parameter and returns an encrypted string.  The C# code for this function is below

public string EncryptString(string ClearText)
        {

            byte[] clearTextBytes = Encoding.UTF8.GetBytes(ClearText);

            System.Security.Cryptography.SymmetricAlgorithm rijn =  SymmetricAlgorithm.Create();

            MemoryStream ms = new MemoryStream();
            byte[] rgbIV = Encoding.ASCII.GetBytes("ryojvlzmdalyglrj"); 
            byte[] key = Encoding.ASCII.GetBytes("hcxilkqbbhczfeultgbskdmaunivmfuo"); 
            CryptoStream cs = new CryptoStream(ms, rijn.CreateEncryptor(key, rgbIV), 
	   CryptoStreamMode.Write);

            cs.Write(clearTextBytes, 0, clearTextBytes.Length);

            cs.Close();

            return Convert.ToBase64String(ms.ToArray());
        }

The function to go from an encrypted string to clear text will do everything in the function above but in the opposite order. 

private string DecryptString(string EncryptedText)
        {
            byte[] encryptedTextBytes = Convert.FromBase64String(EncryptedText);

            MemoryStream ms = new MemoryStream();

            System.Security.Cryptography.SymmetricAlgorithm rijn = SymmetricAlgorithm.Create();


            byte[] rgbIV = Encoding.ASCII.GetBytes("ryojvlzmdalyglrj"); 
            byte[] key = Encoding.ASCII.GetBytes("hcxilkqbbhczfeultgbskdmaunivmfuo"); 

            CryptoStream cs = new CryptoStream(ms, rijn.CreateDecryptor(key, rgbIV), 
            CryptoStreamMode.Write);

            cs.Write(encryptedTextBytes, 0, encryptedTextBytes.Length);

            cs.Close();

            return Encoding.UTF8.GetString(ms.ToArray());

        }

Be sure to replace the encryption keys I provided above with your own unique values.  This site is an easy way to generate random letters of a given length.  Also, note that the encryption type used above is symmetric t.  If you need an asymmetric or hash algorithm use the appropriate class within the Cryptography namespace.

Tuesday, December 16, 2008

If you receive an error like the following for a LINQ expression "Could not find an implementation of the query pattern for source type 'Microsoft.SharePoint.SPWebCollection'.  'Where' not found.  Consider explicitly specifying the type of the range variable" the reason is either that the object does not implement IEnumerable or that the compiler needs to be explicity told what to enumerate.  In the above case Microsoft.SharePoint.SPWebCollection is enumerable for type SPWeb so to fix the error the code needs to be modified from this

IEnumerable<SPWeb> webs = from web in SPContext.Current.Site.AllWebs where web.Permissions.DoesUserHavePermissions(SPRights.ViewListItems) select web;

To this

IEnumerable<SPWeb> webs = from SPWeb web in SPContext.Current.Site.AllWebs where web.Permissions.DoesUserHavePermissions(SPRights.ViewListItems) select web;

Monday, December 15, 2008

Before connecting to an Oracle database from SSIS, there are two prerequisites that must be completed.  First, the client drivers must be installed on the server running the SSIS package.  Secondly, the tnsnames.ora configuration file must be set up with the appropriate connection information.


There are many client drivers that correspond to different versions of the Oracle database server and the client OS.  They can be downloaded here from the Oracle website (registration required).  Once the drivers have been downloaded and extracted, installation can begin by running setup.exe.  On the select installation type, the Runtime or Administrator option should be selected.  After selecting a file location the drivers will install on the system.  Assuming setup completes successfully, the tnsnames.ora file is ready to be modified.
The TNSNAMES.ora file instructs the oracle driver where and how to connect to an Oracle Database.  Under the default installation file path, a sample TNSNAMES.ora file can be found C:\oracle\product\[database version]\client_1\network\ADMIN\SAMPLE.  However, the actual TNSNAMES.ora should be place one directory up in the ADMIN folder.  For more information on TNSNAMES refer to the Oracle documentation


With the above steps completed the connection can now be established from within SSIS.  Because the OLE DB Source and Destination shapes rely on OLE DB Connections, the connection must first be created.  To complete this, right click on the Connection Manager pane and Select New OLE DB Connection.   In the provider drop down list select Oracle Provider for OLE DB.  In the Server or file name textbox enter the connection name defined in your tnsnames.ora file.  Finally, enter your username and password and click Test Connection.


If the connection is not successful the TNSPing utility can be used to troubleshoot any issues.  It can be found default file location is C:\oracle\product\[database version\client_1\bin.

Friday, November 28, 2008

Knowing the IDE keyboard shortcuts is one of the easiest ways to increase coding productivity.  Think about how much of a bottleneck the mouse can be.  It uses 4 commands:  horizontal movement, vertical movement, left click, and right click.  Communicating with a complex editor with these four commands is like trying to chop down a tree with just one hand.  Here is a reference of the most commonly used keybindings for Visual Studio 2008.  However, these are only the defaults.  You can customize your environment from the Tools > Options > Envrionment > Keyboard screen.

Wednesday, October 01, 2008

You may notice the Edit Page option missing from the Site Actions menu on pages that use the list item templates (by default they are AllItems.aspx, EditForm.aspx, NewForm.aspx, DispForm.aspx).  First, make sure you have a web part zone on the page.  The site actions won't display Edit Page option if the page doesn't contain a single zone.

<WebPartPages:WebPartZone runat="server" Frametype="TitleBarOnly" id="Zone1" Title="loc:Right"><ZomeTemplate></ZomeTemplate></WebPartPages:WebPartZone>

However, even with the zone, the Edit Page option doesn't always appear.  This is a bug with SharePoint.  A workaround is to make sure the page contains at least one web part.  You can do this in SharePoint Designer by clicking on the "Click to insert a Web Part" link inside the webpart zone and then dragging a web part from the Web Part List menu.

Saturday, August 02, 2008

SQL Server Integration Services has a significant performance issue with large result sets returned by SQL’s “FOR XML” clause.  The problem is due to memory consumption and the way SSIS’s data flow engine handles row-based results.  When SSIS receives a data stream, it buffers a portion of the stream, performs the necessary operations on the rows in the buffer, clears the buffer, and repeats the process until there is no more data remaining in the stream.  The problem with the way FOR XML returns data is that it doesn’t return rows, but rather a result set with a single row, which is seen by SSIS as a large Binary Large Object ( BLOB). 

The initial reaction many developers have to this issue is to decrease the DefaultBufferSize property of data flow task.  Unfortunately, this won’t help at all since the buffer is not able work with fractional rows, which means the buffer(s) will continue to fill beyond their threshold until a whole number of rows is reached.  With a single row, this means the entire stream will be loaded into memory.  SQL 2005 and above provide a solution to this problem with the ability to shred the xml data type into individual rows.  Compare the result returned by the below queries.
DECLARE @XML xml

SET @XML = (SELECT
UserID,
FirstName,
LastName
FROM
Users
FOR XML PATH('User'))
SELECT @XML AS UserXML
results with a single row
DECLARE @XML xml

SET @XML = (SELECT
UserID,
FirstName,
LastName
FROM
Users
FOR XML PATH('User'))


SELECT nref.query('.') AS UserXML
from @XML.nodes('//User') AS R(nref)
results with multiple rows SSIS can efficiently buffer the result set returned by the second query, which will enable large sets to be processed without issue.

Thursday, July 24, 2008

If you receive the following error on a front-end web server of a WSS 3.0 farm while performing a search against Search Server 2008

Object reference not set to an instance of an object.   at Microsoft.Office.Server.Search.WebControls.CoreResultsWebPart.OnLoad(EventArgs e)
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

First make sure you have the front-end web server component of Search Server 2008 installed on the server where you are performing the search.  Next, add the URL you are using to access the server in your alternate access mappings.  Within Central Administration, navigate to Operations -> Global Configuration -> Alternate Access Mappings.  "Select Add Internal URLs".  Select your web application in the Alternate Access Mapping Collection drop down list.  Specify the path and port you use to access the application (example:  http://machinename01:80).  Click Save.  There is no need for an IIS reset.  The change should immediately take effect .

Tuesday, July 01, 2008

A scenario I've repeatedly encountered in database development is to create a stored procedure that accepts a range of values as a comma delimited list and returns a result set that contains at least one of the values from the list.  To do this, the parameter value must be parsed as something that can be understood by the IN clause.  I prefer to turn the delimited list into a single tabled column.  Because this task is so common, I wrote the following function that takes a comma delimited string and returns a table variable.
CREATE FUNCTION [dbo].[fnCommaDelimitedToTable]
(
@CommaDelimited varchar(5000)
)
RETURNS @returntable TABLE
(
FilterColumn varchar(50)
)
AS
BEGIN
SET @CommaDelimited = REPLACE(@CommaDelimited, ', ', ',')
WHILE CHARINDEX(',' , @CommaDelimited) > 0
BEGIN
INSERT INTO
@returntable
VALUES
(
SUBSTRING(@CommaDelimited, 0, CHARINDEX(',', @CommaDelimited))
)

SET @CommaDelimited = SUBSTRING(@CommaDelimited, CHARINDEX(',', @CommaDelimited) + 1,
LEN(@CommaDelimited))
END
-- Insert the last one, or if there was only one supplied.
INSERT INTO
@returntable
VALUES
(
@CommaDelimited
)
RETURN
END
With this function you can now write queries like
SELECT *
FROM
Customers
WHERE
Name IN (SELECT SearchFilter FROM dbo.fnCommaDelimitedToTable(@DelimitedNames))

Tuesday, June 24, 2008

In SQL Server Integration Services (SSIS) the DataReader Source component is analogous to ADO.NET's DataReader classes.  It retrieves a Binary Large Object (BLOB) stream from the data source and is therefore efficient and the least expensive option in terms of memory.  Having said that, there are times when it is convenient to convert the returned BlobColumn into a string object.  Within a script component, the following will build a binary array from the BLOB stream and then encode the binary array to a Unicode string.

Dim result As String = _
System.Text.Encoding.Unicode.GetString(Row.FieldName.GetBlobData(0, CInt(Row.FieldName.Length)))

Again, one should be cautious when doing this.  It is similar to using the DataAdapter class to convert a DataReader to a DataSet, so depending on the size of the data, this may or may not be a good idea.

Monday, April 28, 2008

Microsoft is offereing Visual Studio 2008 along with Windows Server 2003, Expression Studio, and XNA game studio at no cost to college students.  Visit the Microsoft DreamSpark site to register and download .  Enjoy.

Saturday, April 12, 2008

Disabling a range of dates on a calendar control is a great way to reduce data-entry error.  For example, imagine a scheduling system for an organization whose doors are open Monday through Friday.  Ideally, any calendar controls should disable Saturday and Sunday to prevent users from accidentally choosing them.  This can be accomplished by taking advantage of the ondayrender event of the calendar control.  First, in your aspx  markup, wire up a method to the event.

<asp:Calendar Visible="false" ID="DisabledWeekendsCalendar" runat="server" ondayrender="DisabledWeekendsCalendar_DayRender"></asp:Calendar>

Finally, in your .NET code, create the method which checks to see if a day is a weekend, and if so disables it.

protected void DisabledWeekendsCalendar_DayRender(object sender, DayRenderEventArgs e)
    {
        if (e.Day.Date.DayOfWeek == DayOfWeek.Saturday || e.Day.Date.DayOfWeek == DayOfWeek.Sunday)
        {
            e.Day.IsSelectable = false;
            e.Cell.ForeColor = System.Drawing.Color.Gray;
        }
    }

Tuesday, April 01, 2008

With Microsoft SQL Server 2005, paging on the database side is simple.  There is a built-in ROW_NUMBER function designed specifically for this task.  However, versions of SQL Server prior to 2005 do not have this capability.  Below is one way to implement similar functionality in pre-2005 versions. 

SELECT 
TOP X *
FROM
Records
WHERE
RecordID NOT IN (SELECT TOP Y RecordID FROM Records)

You must replace X and Y with actual integer values, Unfortuantly, due to sql sytanx, you can’t use varaibles.  The records returned from the above query will follow

Y + 1 = Beginning record
X + Y + 1 = Ending Record

So 10 and 20 plugged in to X and Y respectively would return records 21 thorugh 31.

Thursday, March 20, 2008

Microsoft just released the “Microsoft BizTalk Server Operations Guide” document.  They describe it as, “…detailed information for planning a BizTalk Server environment, as well as recommendations and best practices for configuring, testing, maintaining, monitoring, and optimizing this environment.”  You can view it from MSDN or download a copy for offline use.

Saturday, March 15, 2008

To debug a piece of .NET code called by the BRE you must first attach the Visual Studio debugger to the BRE process.   To accomplish this, open the debug menu and select “attach to process…” and select the Microsoft.RuleComposer process.  Now you can put breakpoints on the desired lines of .NET code and when the rule executes from the BRE, the execution will catch at the first breakpoint.
 
Facts can be asserted to the BRE from .NET code simply by passing them as an argument to the policy’s execute method which takes a variable number of parameters.  For example,

Microsoft.RuleEngine.Policy policy = new Microsoft.RuleEngine.Policy(“PolicyName”);
Policy.Execute(Object1, Object2,…);

There are three types of facts used by the BRE:  An xml document, a Database table, and a .NET class.  The latter two have a couple peculiarities about them which require further explanation. 

Asserting a Database Table

When using a database table, the BRE expects an object of type Microsoft.RuleEngine.DataConnection to be asserted, which tells the BRE where to find the table.  This is achieved with the below code.

//Create the DataConnection
System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection(“connectionstring”);
Microsoft.RuleEngine.DataConnection dconn = new Microsoft.RuleEngine.DataConnection(connection);

//Create the policy and assert the DataConnection
Microsoft.RuleEngine.Policy policy = new Microsoft.RuleEngine.Policy(“PolicyName”);
Policy.Execute(dconn);

Static .NET Methods

Calling a static .NET method from the BRE can be achieved without asserting an instance of the class by setting the the StaticSupport (DWORD value) registry key located under HKEY_LOCAL_MACHINE\Software\Microsoft\BusinessRules\3.0 to a value of 1.  Without this registry entry, static method calls within the BRE will require asserting instances of the class that contain the method.
 
Even though the Business Rules Engine (BRE) is included with BizTalk 2006, it does not need to be exclusively called by the BizTalk host.  The BRE has a .NET API library that enables any host to communicate with the engine programmatically.
You must first reference the Microsoft.BizTalk.RuleEngineExtensions.dll from the BizTalk installation directory (the default installation is C:\Program Files\Microsoft BizTalk Server 2006).

You can then call BRE policies with the following code

Microsoft.RuleEngine.Policy policy = new Microsoft.RuleEngine.Policy(“PolicyName”);
Policy.Execute();

The execute method can take any number of objects as a parameter that need to be asserted as facts to the BRE for execution.

Thursday, March 06, 2008

If you are using the Business Rules Engine (BRE) as part of BizTalk or Windows Workflow Foundation and are asking yourself “Why are my rules firing in the wrong order?” you probably need a better understanding of how the BRE executes a set of rules (policy).  The priority property of a rule will control the order in which the action of that rule will fire relative to the other actions in a policy.  Specifically, an action of a rule with a higher priority will fire before an action of a rule with a lower priority.  Notice it is the action of a rule which is affected by the priority and not the evaluation of the rule.  The evaluations themselves will still occur in an arbitrary order.  For example, assume three rules exist in the same policy, “Rule 1”, “Rule 2”, and “Rule 3” with their priority properties set to 3, 2, and 1 respectively.   When the policy is invoked, Rule 2 may be evaluated, then rule 3, and finally rule 1.  Only after evaluation of all rules will the actions of the rules fire in the order specified by the priority values.  What are the practical consequences of this?  A rule dependent on the action of a previous rule within the same policy won’t evaluate as expected. 


There are a couple ways to overcome this problem.  The first is to place rules dependent on each other in separate polices.  This requires the host application to call multiple policies and to be knowledgeable about the order in which they should be called, a detail which should be abstracted from the calling process.  The second technique is to use an update action to let the BRE know an object has changed its state and that any rules that had evaluated this fact need to be re-evaluated.  The process look like this

  1. Execute all rules of a policy (these will be evaluated in an arbitrary order)
  2. Fire any Actions for any rules that evaluated to true  (these will be fired according to the rule’s priority property)
  3. Check if any facts used the update method to notify the BRE that a fact changed its state.
  4. Re-evaluate any rules that contained updated facts
  5. Repeat 2-4 until no facts are updated