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