CodingPleasure

cultivate passion for everything else that goes on around programming

Monthly Archives: June 2010

Audit Trail with EntityReferences in Entity Framework 3.5

Tracking changes in Entity Framework 3.5 is pretty straight forward. I used the techniques described here: Audit Trail Using EF Part 1

Everything was fine except that the code in this article did not track changes to EntityReferences. So given an object A that has a reference to object B, the Entity Framework does not mark the reference from A to B as changed. Instead, when the reference changes, it deletes the previous reference and adds the new reference as an ObjectStateEntry. This way, when calling ObjectStateManager.GetObjectStateEntries(), there will be 2 objects returned, one with the EntityState.Deleted state and one with the EntityState.Added state. Additionally, the ObjectStateEntry.GetModifiedProperties() method does not return any information about the change of the reference.

So I adapted the code from the article to retrieve the changed reference values too. The following code shows how I did that, based on the code from the original article.

   1: /// <summary>
   2: /// Gets the entity properties as XML, either the original values or the current values.
   3: /// </summary>
   4: /// <param name="entry">The entry.</param>
   5: /// <param name="isOrginal">if set to <c>true</c> then the original values will be serialized.</param>
   6: /// <returns>XML string of serialized entity</returns>
   7: private string GetEntityPropertiesAsXml(ObjectStateEntry entry, bool isOrginal)
   8: {
   9:     if (entry.Entity is EntityObject)
  10:     {
  11:         object target = HelperClasses.CloneEntity((EntityObject)entry.Entity);
  12:         foreach (string propName in entry.GetModifiedProperties())
  13:         {
  14:             object setterValue = null;
  15:             if (isOrginal)
  16:             {
  17:                 //Get original value
  18:                 setterValue = entry.OriginalValues[propName];
  19:             }
  20:             else
  21:             {
  22:                 //Get original value
  23:                 setterValue = entry.CurrentValues[propName];
  24:             }
  25:  
  26:             //Find property to update
  27:             PropertyInfo propInfo = target.GetType().GetProperty(propName);
  28:  
  29:             //update property with original value
  30:             if (setterValue == DBNull.Value)
  31:             {
  32:                 setterValue = null;
  33:             }
  34:             propInfo.SetValue(target, setterValue, null);
  35:         }
  36:  
  37:         // references are not returned by GetModifiedProperties(), so we need to search through all
  38:         // object state entries for those matching our properties and get the deleted ones
  39:         var deletedRelations = entry.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Where(ose => ose.IsRelationship);
  40:         var addedRelations = entry.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(ose => ose.IsRelationship);
  41:         foreach (PropertyInfo pi in target.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(pi => pi.PropertyType.BaseType == typeof(EntityReference)))
  42:         {
  43:             // try to get the original values
  44:             if (isOrginal)
  45:             {
  46:                 EntityReference currentReference = (EntityReference)pi.GetValue(target, null);
  47:  
  48:                 // find the value of the old relation in the deletedRelations
  49:                 var oldRelation = deletedRelations
  50:                     .Where(relation => relation.EntitySet.ElementType.FullName == currentReference.RelationshipName)
  51:                     .FirstOrDefault();
  52:  
  53:                 if (oldRelation != null)
  54:                 {
  55:                     // set the current value to the old reference
  56:                     currentReference.EntityKey = (oldRelation.OriginalValues[0] as EntityKey);
  57:                 }
  58:                 else
  59:                 {
  60:                     var addedRelation = addedRelations
  61:                         .Where(relation => relation.EntitySet.ElementType.FullName == currentReference.RelationshipName)
  62:                         .FirstOrDefault();
  63:  
  64:                     // if an added relation was found, but a deleted one was not found
  65:                     if (addedRelation != null)
  66:                     {
  67:                         // object did not have any relation attached previously
  68:                         currentReference.EntityKey = null;
  69:                     }
  70:                 }
  71:             }
  72:         }
  73:  
  74:         XmlSerializer formatter = new XmlSerializer(target.GetType());
  75:         XDocument document = new XDocument();
  76:         using (XmlWriter xmlWriter = document.CreateWriter())
  77:         {
  78:             formatter.Serialize(xmlWriter, target);
  79:         }
  80:  
  81:         return document.Root.ToString();
  82:     }
  83:  
  84:     return null;
  85:  
  86: }

The interesting part begins at line 39, where I try to find all relationships, added or deleted, that are related to the given entry. By finding the deleted references we are able to set the original value of the EntityKey. If no deleted reference is found, we try to find an added relation. If an added relation exists, but no deleted one, than we can assume that the reference was not set before the change, so we set it’s EntityKey to null.

Thus we can store our OldData and our NewData into the audit trail database.

C# vs PHP Encryption using Rijndael/AES

While trying to encrypt and decrypt some strings in PHP and in C# using the same algorithm, I wasn’t able to found a good hint on the Internet. After some research I came to the following conclusions:

The following encryption codes are equivalent and will produce the same result. But with one additional note, worth to mention: Although in PHP the key’s length is not important, in C# you must use a key with the length of 32 bytes!

   1: $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
   2: $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
   3: $key = $pwd_key;//"This is a very secret key";
   4: return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv));
   1:  
   2:             // set up encrytion object
   3:             RijndaelManaged aes256 = new RijndaelManaged();
   4:             aes256.KeySize = 256;
   5:             aes256.BlockSize = 256;
   6:             aes256.Padding = PaddingMode.Zeros;
   7:             aes256.Mode = CipherMode.ECB;
   8:             aes256.Key = ASCII_ENCODING.GetBytes(passKey);
   9:             aes256.GenerateIV();
  10:  
  11:             // form the string for encrypting
  12:             // and put into byte array
  13:             byte[] plainTextBytes = ASCII_ENCODING.GetBytes(message);
  14:  
  15:             // encrypt the data            
  16:             ICryptoTransform encryptor = aes256.CreateEncryptor();
  17:             MemoryStream ms = new MemoryStream();
  18:  
  19:             CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
  20:             StreamWriter mSWriter = new StreamWriter(cs);
  21:  
  22:             mSWriter.Write(message);
  23:             mSWriter.Flush();
  24:             cs.FlushFinalBlock();
  25:  
  26:             // convert our encrypted data from a memory stream into a byte array.
  27:             byte[] cypherTextBytes = ms.ToArray();
  28:  
  29:             // close memory stream
  30:             ms.Close();
  31:  
  32:             return Convert.ToBase64String(cypherTextBytes);

Additional resources:

Usefull resource related to the same topic

Example of code conversion on stackoverflow.com

Multiple Instances of Reporting Services 2008, one with custom authentication

Using Forms Authentication with Reporting Services 2008 worked fine based on the sample from code project:

http://msftrsprodsamples.codeplex.com/wikipage?title=SS2008!Security%20Extension%20Sample&referringTitle=Home

But after I installed a second instance of Reporting Services 2008 on the same machine, the security extension did no longer work as expected. The problem was that the newly installed instance was using SSL, the first one did not. When I tried to login to the Reporting Services 2008 first instance, I received “The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel”. In the log files there was this error: “Remote certificate error RemoteCertificateChainErrors encountered for url”. This is how I found out that the security extension did not use the proper report server URL!

After debugging, I found the following problem and solution:

The security extension uses WMI in the following method to determine the ReportServerUrl:

internal static string GetReportServerUrl(string machineName, string instanceName)

When multiple instances are running, we have 2 ManagementObjects, even if we use the correct WMI path! The following picture shows that there are 2 MSReportsServer_Instance instances. imageSo

I changed the code to this:

foreach (ManagementObject instance in instances)
{
   instance.Get();

    // we are interested only for a specific instance
   var instanceNameProperty = instance.GetPropertyValue("InstanceName");
    if (instanceNameProperty != null && String.Compare(instanceNameProperty.ToString(), instanceName, true) != 0)
    {
        continue;
    }
...
}

This way I correctly retrieve the reports’ server url of the specified instance.

For performance reasons, I think it would be much easier to hard code the URLs value into the web.config and not query the value through WMI. The disadvantage would be that if you change the Reporting Services 2008 configuration, you need to manually edit the web.config of the ReportServer too.

Reporting Services, Report Builder and Forms Authentication

Thank god I found this link: http://blogs.infosupport.com/blogs/marks/archive/2010/03/29/report-builder-2-0-security-extension-issues.aspx

I stumbled upon the same problem which is described there: Report Builder used to freeze when using Forms Authentication and using Report Builder 2.0.

As it turned out, the Report Builder does not send the authentication cookie with each request. Because of this, the authentication extension returned a object moved error, but the Report Builder 2.0 did not treat it in any way. Making sure that we return null as mentioned in the blog, the extension throws an exception (visible in the Report Server log files), but the Report Builder 2.0 works as expected.

 

Additional notes: I created the custom security extension based on the steps described here:

http://msftrsprodsamples.codeplex.com/wikipage?title=SS2008!Security%20Extension%20Sample&referringTitle=Home

Besides what’s described there, I think I also gave change rights to NETWORK SERVICE for

c:\Windows\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\

and in AuthenticationUtilities.cs, I changed the following line:

 

   1: string fullWmiNamespace = @"\\" + machineName + string.Format(wmiNamespace, "RS_" + instanceName);

 

Now everything seems to work properly using Forms Authentication on Reporting Services!

Reporting Services Adventures

I starting working with Reporting Services 2008 in a project at work. I didn’t really knew much about the whole technology around SSRS, but after some weeks/month of researching I managed to get everything working.

The customers’ requirements were not that trivial. Starting from “simple” ones like multi-language and up to more difficult ones like reports getting data from multiple databases, I learned that there are a lot of options and different ways to implement reports.

The journey began, like every project, with the client’s whishes, listed in a document, known as the specifications document. The main requirements were:

  1. Reports should be viewable via a web browser.
  2. The same report, viewed by different users, should display different data.
  3. Users must be able to create their own reports.
  4. Multi-language: reports should be localizable.

Broken into smaller pieces, each requirement has been investigated and possible solutions were analyzed. After some Internet researching, some e-books and even a printed book (in German language), I have gathered some answers to some of the problems, but unfortunately even more questions than I expected. The difficult part was that I didn’t have enough experience with reporting tools (I played with Crystal Reports some years ago, but never did any web based reports). Diving into the SSRS technology, I begun to understand the concept behind a report server, why someone should use it after all and what the benefits are.

During the research, I gathered a list of useful links:

  1. How to set up Report Manager to start Report Builder 2.0 as a ClickOnce application: http://msdn.microsoft.com/en-us/library/dd795295.aspx
  2. Information about Connection Strings in Report Builder (event expression based connection string) http://msdn.microsoft.com/en-us/library/ms156450.aspx
  3. Howto start Report Builder using parameters: http://msdn.microsoft.com/en-us/library/ms345245.aspx
  4. Howto customize the ReportViewer control using URL parameters http://msdn.microsoft.com/en-us/library/ms152835.aspx
  5. Blog about SQL Server Reporting Services http://blogs.msdn.com/bobmeyers/

Coming next: information about (almost) every problem I stumbled upon using SSRS.

Optimizing Entity Framework 3.5

In
a struggle to squeeze the most of EF, I found this link:

http://msdn.microsoft.com/en-us/library/bb896240.aspx

 

The
performance problem for the first ObjectContext initialization is the
generating of the context’s views. These are some internal used views, which
the developer should not know about. But the generation at run-time is
time-consuming and can be done up-front, at compile/build time. So I followed
the steps mentioned in the link above and finally managed to get the following
measured performance improvement:

 

 

The
highlighted line is a ObjectContext with about 60 entities. I’m pretty
satisfied with the Speedup improvement
J
All in one, I could say that after pre-compiling the view, for the particular
scenario I profiled, the response was about 1 second faster.

 

After
doing all this I found the following link on CodeProject:

http://www.codeproject.com/KB/database/PerfEntityFramework.aspx

As
mentioned there, the pre- generation of the view is the practice with the most
impact on the performance of the Entity Framework. Another optimization I will
implement next: precompiled queries!