Skip to main content

DrySQL for the Microsoft MVC .NET framework

The sample Chapter 1 from Professional ASP.NET MVC 1.0 (http://aspnetmvcbook.s3.amazonaws.com/aspnetmvc-nerdinner_v1.pdf) is a great tutorial that guides you through the features of Microsoft's MVC framework. If you have used Rails before, when you read the tutorial you keep telling to yourself "wow, this is like rails but compiled and with excellent Intellisense and a nice and fast database backend". That's right, SQL Server is tightly coupled to the Linq to SQL framework that you can use as the ORM for the MS MVC (please note that the use of Linq to SQL is optional; and you could use other ORM frameworks). Just because is tightly coupled does not mean that is a bad idea; it actually integrates really well with the visual tools from Visual Studio. Here is a screen shot showing a basic overview of the database ORM mapping tool in MS Vistual Web Developer Express (which is free, like all the technologies described here, including the SQL server 2008 Express edition):

As I was reading the tutorial, I found that they fixed some of the disadvanteges in Rails mentioned in the Enterprise Rails book from O'Reilly. The thing I was still missing tho, was that Linc to SQL does not generate automatic basic Database derived validations such as the length of fields and nullability of fields which is something that Rails misses to and was solved by DrySQL. The principle behind DrySQL is that if you already defined it in your DBMS, why should you defined again in your code?

To solve this I modify the RuleViolation class that you write in the tutorial to validate the Model's fields against constraints defined in the database such as the length of fields and nullability:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Data.Linq.Mapping;

namespace NerdDinner.Models
{
public class RuleViolationException : ApplicationException
{
   public RuleViolationException(string errorMessage)
       : base(errorMessage)
   {

   }
}
public class RuleViolation
{
   public string ErrorMessage { get; private set; }
   public string PropertyName { get; private set; }
   public RuleViolation(string errorMessage)
   {
       ErrorMessage = errorMessage;
   }
   public RuleViolation(string errorMessage, string propertyName)
   {
       ErrorMessage = errorMessage;
       PropertyName = propertyName;
   }

//Parts of this method were taken from
// http://www.codeproject.com/KB/cs/LinqColumnAttributeTricks.aspx?display=Print
// * Checks that fields required fields in the database (not null flagged) are present
// * Checks that values are numbers for numeric fields
// * Checks that string values are not too big for the db field
   public static List<RuleViolation> CheckBasicDBCobnstraints(object obj)
   {
       List<RuleViolation> violations = new List<RuleViolation>();
       Type type = obj.GetType();
       object[] info;
       PropertyInfo[] properties = type.GetProperties();
       foreach (PropertyInfo prop in properties)
       {
           //Get the Linq realted info
           info = prop.GetCustomAttributes(typeof(ColumnAttribute), true);
           // Check if it is a Linq column property
           if (info.Length == 1)
           {
               ColumnAttribute ca = (ColumnAttribute)info[0];
               string dbtype = ca.DbType;
               //Check if it is text
               if (dbtype.StartsWith(\"NChar\") || dbtype.StartsWith(\"NVarChar\"))
               {
                   bool property_is_empty = false;
                   string property_str_value = (string) prop.GetValue(obj, null);
                   //Check if it is required or not
                   if (!ca.CanBeNull)
                   {
                       if (String.IsNullOrEmpty(property_str_value))
                       {
                           violations.Add(new RuleViolation(prop.Name + \" is required\", prop.Name));
                           property_is_empty = true;
                       }
                   }
                   //check the length
                   if (!property_is_empty)
                   {

                       int dblenint = 0;
                       int index1 = dbtype.IndexOf(\"(\");
                       int index2 = dbtype.IndexOf(\")\");
                       string dblen = dbtype.Substring(index1 + 1, index2 - index1 - 1);
                       int.TryParse(dblen, out dblenint);
                       if (property_str_value.Length > dblenint)
                           violations.Add(new RuleViolation(prop.Name + \" too long (Max. length is \" + dblenint.ToString() + \")\", prop.Name));
                   }
               }
               else
               {
                   object property_str_value = prop.GetValue(obj, null);
                   bool property_is_empty = (property_str_value == null);
                   //Check if it is required or not
                   if (!ca.CanBeNull)
                   {
                       if (property_is_empty)
                       {
                           violations.Add(new RuleViolation(prop.Name + \" is required\", prop.Name));
                           property_is_empty = true;
                       }
                   }
               }
           }
       }
       return violations;
   }
}
}

Then I changed the Dinner class to use the new method:

   public List<RuleViolation> GetRuleViolations()
   {
       List<RuleViolation> violations = RuleViolation.CheckBasicDBCobnstraints(this);
       violations.AddRange(GetCustomRulesViolations());
  
       return violations;
   }

   //Check for any custom constraint on the fields
   private List<RuleViolation> GetCustomRulesViolations()
   {
       List<RuleViolation> violations = new List<RuleViolation>(1);
       if (!PhoneValidator.IsValidNumber(ContactPhone, County))
           violations.Add( new RuleViolation(\"Phone# does not match country\",
           \"ContactPhone\"));
       return violations;
   }

   partial void OnValidate(ChangeAction action)
   {
       if (!IsValid)
           throw new RuleViolationException(\"Rule violations prevent saving\");
   }

Please follow the tutorial first so that you can get as feel of the power of MS MVC, then you download my complete modification here to use DrySQL in .NET.

Comments

Popular posts from this blog

Powershell script for converting JPG to TIFF

The following Powershell script will convert a batch of JPEG files to TIFF format: #This Code is released under MIT license [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") $files_folder = 'C:\path-where-your-jpg-files-are\' $pdfs = get-childitem $files_folder -recurse | where {$_.Extension -match "jpg"} foreach($pdf in $pdfs) { $picture = [System.Drawing.Bitmap]::FromFile( $pdf.FullName ) $tiff = $pdf.FullName.replace('.PDF','').replace('.pdf','').replace('.jpg','').replace('.JPG','') + '.tiff' $picture.Save($tiff) }

Power Automate: SFTP action "Test connection failed"

When I added an SFTP create file action to my Power Automate flow ( https://flow.microsoft.com ) , I got the following error in the action step, within the designer: "Test connection failed" To troubleshoot the Power Automate connection, I had to: go the Power Automate portal then "Data"->"Connections"  the sftp connection was there, I clicked on the ellipsis, and entered the connection info It turns out, that screen provides more details about the connection error. In my case, it was complaining that "SSH host key finger-print xxx format is not supported. It must be in 'MD5' format". I had provided the sha fingerprint that WinScp shows. Instead, I needed to use the MD5 version of the fingerprint. To get that, I had to run in command line (I was in a folder that had openssh in it): ssh -o FingerprintHash=md5 mysftpsite.com To get the fingerprint in MD5 format. I took the string (without the "MD5:" part of the string) and put

Alert if file missing using Powershell

The following Powershell script can be used to send an email alert when a file is missing from a folder or it is the same file from a previous check: $path_mask = "yourfile_*.txt" $previous_file_store = "lastfileread.txt" $script_name = "File Check" ###### Functions ########## Function EMailLog($subject, $message) {    $emailTo = "juanito@yourserver.com"    $emailFrom = "alert@yourserver.com"    $smtpserver="smtp.yourserver.com"       $smtp=new-object Net.Mail.SmtpClient($smtpServer)    $smtp.Send($emailFrom, $emailTo, $subject, $message) } Try {    #get files that match the mask    $curr_file = dir $path_mask |  select name    if ($curr_file.count -gt 0)    {        #file found        #check if the file is different from the previous file read        $previous_file = Get-Content $previous_file_store        $curr_file_name = $curr_file.Item(0).Name        if ($