• Blog
  • Info Support
  • Career
  • Training
  • International Group
  • Info Support
  • Blog
  • Career
  • Training
  • International Group
  • Search
logo InfoSupport
  • Latest blogs
  • Popular blogs
  • Experts
      • Alles
      • Bloggers
      • Speakers
  • Meet us
  • About us
    • nl
    • en
    • .NET
    • Advanced Analytics
    • Agile
    • Akka
    • Alexa
    • Algorithms
    • Api's
    • Architectuur
    • Artificial Intelligence
    • ATDD
    • Augmented Reality
    • AWS
    • Azure
    • Big Data
    • Blockchain
    • Business Intelligence
    • Cloud
    • Code Combat
    • Cognitive Services
    • Communicatie
    • Containers
    • Continuous Delivery
    • CQRS
    • Cyber Security
    • Dapr
    • Data
    • Data & Analystics
    • Data Science
    • Data Warehousing
    • Databricks
    • DevOps
    • Digital Days
    • Docker
    • eHealth
    • Enterprise Architecture
    • Hacking
    • Infrastructure & Hosting
    • Innovatie
    • Integration
    • Internet of Things
    • Java
    • Machine Learning
    • Microservices
    • Microsoft
    • Microsoft Bot Framework
    • Microsoft Data Platform
    • Mobile Development
    • Mutation Testing
    • Open source
    • Pepper
    • Power BI
    • Privacy & Ethiek
    • Python
    • Quality Assistance & Test
    • Quality Assurance & Test
    • Requirements Management
    • Scala
    • Scratch
    • Security
    • SharePoint
    • Software Architecture
    • Software development
    • Software Factory
    • SQL Server
    • SSL
    • Start-up
    • Startup thinking
    • Stryker
    • Test Quality
    • Testing
    • TLS
    • TypeScript
    • Various
    • Web Development
    • Web-scale IT
    • Xamarin
    • Alles
    • Bloggers
    • Speakers
Home » Sharing validation code between a WPF client, a WCF Service and Entity Framework 4.1 using data annotations
  • Sharing validation code between a WPF client, a WCF Service and Entity Framework 4.1 using data annotations

    • By Alex van Beek
    • Various 11 years ago
    • Various 0 comments
    • Various Various
    Sharing validation code between a WPF client, a WCF Service and Entity Framework 4.1 using data annotations

    Recently Entity Framework 4.1 was released. While most people were very enthusiastic about the code first stuff, code first was the part that impressed me the least. This is because in my line of business we deal with stored procedures a lot and it turns out that mapping to stored procedures isn’t supported with code first in the 4.1 release. What did catch my eye is the new DBContext api. As you’ll probably know, the new DBContext class is largely a wrapper around the ObjectContext class with a simplified API. Now you might think as I did: “But if I’m comfortable with the ObjectContext class and I don’t use code first, why in the world would I need a DbContext?”. The answer is pretty simple. The DbContext has a feature which simply isn’t supported by the ObjectContext: data validation with the attributes from the System.ComponentModel.DataAnnotations namespace. In this article I’ll show you how to decorate your classes with validation attributes and how these classes can be reused by both the client and the server. The client will be a WPF application. If you don’t like WPF or you just don’t care, feel free the skip to the “WCF Service” paragraph. That’s where the Entity Framework 4.1 part is explained.

    The Solution

    Let’s first start with the two projects that are responsible for my data access classes, including the entities:

    image

    Both DbContextLibrary and EntitiesLibrary are normal class library projects. I started with the DBContextLibrary and used the new “DbContext Generator” T4 template to generate classes. When you use this template you’ll actually get two T4 templates added to your project. The one with “.Context” in it generates your DbContext class, the other one generates POCO entities. I’ve simply moved the one that generates the entities to it’s own class library. If you do this, you’ll have to add a namespace in the T4 template that generates the DbContext, so that DbContext knows the entity classes. In this article I will only have one entity, namely Product. I’ve added some validation to this class using the buddy class mechanism. This code is contained in the Product.Metadata.cs file in the EntitiesLibrary project:

       1: using System;

       2: using System.Collections.Generic;

       3: using System.Linq;

       4: using System.Text;

       5: using System.ComponentModel.DataAnnotations;

       6:  

       7:  

       8: namespace EntitiesLibrary

       9: {

      10:     [MetadataType(typeof(ProductMetadata))]

      11:     public partial class Product 

      12:     {

      13:         private class ProductMetadata

      14:         {

      15:             [Range(0,Double.MaxValue,ErrorMessage="ListPrice can't be smaller than zero!")]

      16:             public decimal ListPrice { get; set; }

      17:         }

      18:     }

      19: }

    You can see that I’ve only added validation for the ListPrice property using the Range attribute. Now let’s take a look at the Adventureworks2008.tt T4 template, since it contains some stuff I had to manually edit:

       1: <#@ template language="C#" debug="false" hostspecific="true"#>

       2: <#@ include file="EF.Utility.CS.ttinclude"#><#@

       3:  output extension=".cs"#><#

       4:&nbsp; 

       5: CodeGenerationTools code = new CodeGenerationTools(this);

       6: MetadataLoader loader = new MetadataLoader(this);

       7: CodeRegion region = new CodeRegion(this, 1);

       8: MetadataTools ef = new MetadataTools(this);

       9:&nbsp; 

      10: string inputFile = @"..DbContextLibraryAdventureworks2008.edmx";

      11: EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile);

      12: string namespaceName = code.VsNamespaceSuggestion();

      13:&nbsp; 

      14: EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this);

      15: WriteHeader(fileManager);

      16:&nbsp; 

      17: foreach (var entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name))

      18: {

      19:     fileManager.StartNewFile(entity.Name + ".cs");

      20:     BeginNamespace(namespaceName, code);

      21: #>

      22: using System;

      23: using System.Collections.Generic;

      24: using System.ComponentModel;

      25: using System.ComponentModel.DataAnnotations;

      26: using System.Linq;

      27: using System.Reflection;

      28:&nbsp; 

      29: <#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#> : IDataErrorInfo

      30: {

      31:     static <#=code.Escape(entity)#>()

      32:     {

      33:         //We need to hook this up in order to make the buddy class mechanism work in WPF, Console Applications etc.....

      34:         //Or the Validator methods won't make use of the metadata class.

      35:&nbsp; 

      36:         Type currentType = MethodBase.GetCurrentMethod().DeclaringType;

      37:         object[] attributes = currentType.GetCustomAttributes(typeof(MetadataTypeAttribute),false);

      38:         if(attributes.Length > 0)

      39:         {

      40:             //MetadataType attribute found!

      41:             MetadataTypeAttribute metaDataAttribute = (MetadataTypeAttribute)attributes[0];

      42:             TypeDescriptor.AddProviderTransparent(

      43:                 new AssociatedMetadataTypeTypeDescriptionProvider(

      44:                     currentType, metaDataAttribute.MetadataClassType),currentType);

      45:         }

      46:            

      47:     }

      48:&nbsp; 

      49:     public string Error

      50:     {

      51:         get 

      52:         {

      53:             ValidationContext vc = new ValidationContext(this, null, null);

      54:             List<ValidationResult> result = new List<ValidationResult>();

      55:             if (!Validator.TryValidateObject(this, vc, result))

      56:             {

      57:                 return result.First().ErrorMessage;

      58:             }

      59:             else

      60:             {

      61:                 return null;

      62:             }

      63:         }

      64:     }

      65:&nbsp; 

      66:     public string this[string columnName]

      67:     {

      68:         get 

      69:         {

      70:             ValidationContext vc = new ValidationContext(this, null, null);

      71:             List<ValidationResult> result = new List<ValidationResult>();

      72:             vc.MemberName = columnName;

      73:             if (!Validator.TryValidateProperty(GetType().GetProperty(columnName).GetValue(this, null), vc, result))

      74:             {

      75:                 return result.First().ErrorMessage;

      76:             }

      77:             else

      78:             {

      79:                 return null;

      80:             }

      81:         }

      82:     }

      83:&nbsp; 

      84: <#

      85:     var propertiesWithDefaultValues = entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity && p.DefaultValue != null);

      86:     var collectionNavigationProperties = entity.NavigationProperties.Where(np => np.DeclaringType == entity && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);

      87:     var complexProperties = entity.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == entity);

      88:&nbsp; 

      89:     if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any())

      90:     {

      91: #>

      92:     public <#=code.Escape(entity)#>()

      93:     {

      94: <#

      95:         foreach (var edmProperty in propertiesWithDefaultValues)

      96:         {

      97: #>

      98:         this.<#=code.Escape(edmProperty)#> = <#=code.CreateLiteral(edmProperty.DefaultValue)#>;

      99: <#

     100:         }

     101:&nbsp; 

     102:         foreach (var navigationProperty in collectionNavigationProperties)

     103:         {

     104: #>

     105:         this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=code.Escape(navigationProperty.ToEndMember.GetEntityType())#>>();

     106: <#

     107:         }

     108:&nbsp; 

     109:         foreach (var complexProperty in complexProperties)

     110:         {

     111: #>

     112:         this.<#=code.Escape(complexProperty)#> = new <#=code.Escape(complexProperty.TypeUsage)#>();

     113: <#

     114:         }

     115: #>

     116:     }

     117:&nbsp; 

     118: <#

     119:     }

     120:&nbsp; 

     121:     var primitiveProperties = entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity);

     122:     if (primitiveProperties.Any())

     123:     {

     124:         foreach (var edmProperty in primitiveProperties)

     125:         {

     126:             WriteProperty(code, edmProperty);

     127:         }

     128:     }

     129:&nbsp; 

     130:     if (complexProperties.Any())

     131:     {

     132: #>

     133:&nbsp; 

     134: <#

     135:         foreach(var complexProperty in complexProperties)

     136:         {

     137:             WriteProperty(code, complexProperty);

     138:         }

     139:     }

     140:&nbsp; 

     141:     var navigationProperties = entity.NavigationProperties.Where(np => np.DeclaringType == entity);

     142:     if (navigationProperties.Any())

     143:     {

     144: #>

     145:&nbsp; 

     146: <#

     147:         foreach (var navigationProperty in navigationProperties)

     148:         {

     149:             WriteNavigationProperty(code, navigationProperty);

     150:         }

     151:     }

     152: #>

     153: }

     154: <#

     155:     EndNamespace(namespaceName);

     156: }

     157:&nbsp; 

     158: foreach (var complex in ItemCollection.GetItems<ComplexType>().OrderBy(e => e.Name))

     159: {

     160:     fileManager.StartNewFile(complex.Name + ".cs");

     161:     BeginNamespace(namespaceName, code);

     162: #>

     163: using System;

     164:&nbsp; 

     165: <#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>

     166: {

     167: <#

     168:     var complexProperties = complex.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == complex);

     169:     var propertiesWithDefaultValues = complex.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == complex && p.DefaultValue != null);

     170:&nbsp; 

     171:     if (propertiesWithDefaultValues.Any() || complexProperties.Any())

     172:     {

     173: #>

     174:     public <#=code.Escape(complex)#>()

     175:     {

     176: <#

     177:         foreach (var edmProperty in propertiesWithDefaultValues)

     178:         {

     179: #>

     180:         this.<#=code.Escape(edmProperty)#> = <#=code.CreateLiteral(edmProperty.DefaultValue)#>;

     181: <#

     182:         }

     183:&nbsp; 

     184:         foreach (var complexProperty in complexProperties)

     185:         {

     186: #>

     187:         this.<#=code.Escape(complexProperty)#> = new <#=code.Escape(complexProperty.TypeUsage)#>();

     188: <#

     189:         }

     190: #>

     191:     }

     192:&nbsp; 

     193: <#

     194:     }

     195:&nbsp; 

     196:     var primitiveProperties = complex.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == complex);

     197:     if (primitiveProperties.Any())

     198:     {

     199:         foreach(var edmProperty in primitiveProperties)

     200:         {

     201:             WriteProperty(code, edmProperty);

     202:         }

     203:     }

     204:&nbsp; 

     205:     if (complexProperties.Any())

     206:     {

     207: #>

     208:&nbsp; 

     209: <#

     210:         foreach(var edmProperty in complexProperties)

     211:         {

     212:             WriteProperty(code, edmProperty);

     213:         }

     214:     }

     215: #>

     216: }

     217: <#

     218:     EndNamespace(namespaceName);

     219: }

     220:&nbsp; 

     221: if (!VerifyTypesAreCaseInsensitiveUnique(ItemCollection))

     222: {

     223:     return "";

     224: }

     225:&nbsp; 

     226: fileManager.Process();

     227:&nbsp; 

     228: #>

     229: <#+

     230: string GetResourceString(string resourceName)

     231: {

     232:     if(_resourceManager == null)

     233:     {

     234:         _resourceManager = new System.Resources.ResourceManager("System.Data.Entity.Design", typeof(System.Data.Entity.Design.MetadataItemCollectionFactory).Assembly);

     235:     }

     236:     

     237:     return _resourceManager.GetString(resourceName, null);

     238: }

     239: System.Resources.ResourceManager _resourceManager;

     240:&nbsp; 

     241: void WriteHeader(EntityFrameworkTemplateFileManager fileManager)

     242: {

     243:     fileManager.StartHeader();

     244: #>

     245: //------------------------------------------------------------------------------

     246: // <auto-generated>

     247: // <#=GetResourceString("Template_GeneratedCodeCommentLine1")#>

     248: //

     249: // <#=GetResourceString("Template_GeneratedCodeCommentLine2")#>

     250: // <#=GetResourceString("Template_GeneratedCodeCommentLine3")#>

     251: // </auto-generated>

     252: //------------------------------------------------------------------------------

     253:&nbsp; 

     254: <#+

     255:     fileManager.EndBlock();

     256: }

     257:&nbsp; 

     258: void BeginNamespace(string namespaceName, CodeGenerationTools code)

     259: {

     260:     CodeRegion region = new CodeRegion(this);

     261:     if (!String.IsNullOrEmpty(namespaceName))

     262:     {

     263: #>

     264: namespace <#=code.EscapeNamespace(namespaceName)#>

     265: {

     266: <#+

     267:         PushIndent(CodeRegion.GetIndent(1));

     268:     }

     269: }

     270:&nbsp; 

     271:&nbsp; 

     272: void EndNamespace(string namespaceName)

     273: {

     274:     if (!String.IsNullOrEmpty(namespaceName))

     275:     {

     276:         PopIndent();

     277: #>

     278: }

     279: <#+

     280:     }

     281: }

     282:&nbsp; 

     283: void WriteProperty(CodeGenerationTools code, EdmProperty edmProperty)

     284: {

     285:     WriteProperty(Accessibility.ForProperty(edmProperty),

     286:                   code.Escape(edmProperty.TypeUsage),

     287:                   code.Escape(edmProperty),

     288:                   code.SpaceAfter(Accessibility.ForGetter(edmProperty)),

     289:                   code.SpaceAfter(Accessibility.ForSetter(edmProperty)));

     290: }

     291:&nbsp; 

     292: void WriteNavigationProperty(CodeGenerationTools code, NavigationProperty navigationProperty)

     293: {

     294:     var endType = code.Escape(navigationProperty.ToEndMember.GetEntityType());

     295:     WriteProperty(PropertyVirtualModifier(Accessibility.ForProperty(navigationProperty)),

     296:                   navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,

     297:                   code.Escape(navigationProperty),

     298:                   code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),

     299:                   code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));

     300: }

     301:&nbsp; 

     302: void WriteProperty(string accessibility, string type, string name, string getterAccessibility, string setterAccessibility)

     303: {

     304: #>

     305:     <#=accessibility#> <#=type#> <#=name#> { <#=getterAccessibility#>get; <#=setterAccessibility#>set; }

     306: <#+

     307: }

     308:&nbsp; 

     309: string PropertyVirtualModifier(string accessibility)

     310: {

     311:     return accessibility + (accessibility != "private" ? " virtual" : "");

     312: }

     313:&nbsp; 

     314: bool VerifyTypesAreCaseInsensitiveUnique(EdmItemCollection itemCollection)

     315: {

     316:     var alreadySeen = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

     317:     foreach(var type in itemCollection.GetItems<StructuralType>())

     318:     {

     319:         if (!(type is EntityType || type is ComplexType))

     320:         {

     321:             continue;

     322:         }

     323:&nbsp; 

     324:         if (alreadySeen.ContainsKey(type.FullName))

     325:         {

     326:             Error(String.Format(CultureInfo.CurrentCulture, "This template does not support types that differ only by case, the types {0} are not supported", type.FullName));

     327:             return false;

     328:         }

     329:         else

     330:         {

     331:             alreadySeen.Add(type.FullName, true);

     332:         }

     333:     }

     334:&nbsp; 

     335:     return true;

     336: }

     337: #>

    &nbsp;

    In order for entities to support automatic validation in WPF I had to change the following things in this template:

    • Line 10: The path to the .edmx file. The .edmx is contained in another project and the T4 template by default assumes it&rsquo;s in the same project as the T4 template, so you&rsquo;ll need to adjust this.
    • Lines 24-27: I&rsquo;ve added these namespaces because they are necessary for the the rest of my code.
    • Line 29: I&rsquo;ve changed this line to let my entity implement the IDataErrorInfo interface. You have to understand that in WPF, unlike in Silverlight, the data annotations aren&rsquo;t automatically read by the components (DataGrid). You&rsquo;ll have to implement the IDataErrorInfo interface and validate manually using the data annotations.
    • Line 49-82: These are the members you&rsquo;ll have to implement because of the IDataErrorInfo interface: Error and an indexer. Normally, in the Error property you&rsquo;ll write code to validate the whole object and construct a string with an error message. In the indexer you&rsquo;ll write code that validates a single property. The property name is supplied as an argument and you&rsquo;ll need to construct a string with an error message. In both members I&rsquo;ve used the Validator class to validate. Whenever you are in a situation that you&rsquo;ll need to validate using data annotations and this doesn&rsquo;t happen automatically, you can use the static Validator class to do the validation for you. This isn&rsquo;t difficult, it&rsquo;s just something to keep in mind. To make the validation generic for property validation, you&rsquo;ll have to supply the member name like on line 72. Next to that, you&rsquo;ll have to use some reflection to get the property value in a generic way. This reflection code is found on line 73, it&rsquo;s the first argument of the TryValidatePropertyMethod.
    • Line 31-47: I&rsquo;ve intentionally covered lines 49-82 first. Because now it&rsquo;s clear that I implement the IDataErrorInfo interface in my entity and I use the Validator class to do the validation for me. If you now create a list of Products, add those to a WPF datagrid and use TwoWay binding with validation enabled for the ListPrice, you would think that validation should kick in. This was at least what I thought. Turns out that the whole buddy metadata mechanism isn&rsquo;t something that&rsquo;s supported by the static Validator class. It are the frameworks like WCF RIA Services that add support for the buddy mechanism. Luckily with some googling and reflectoring I came to the conclusion that the Validator class uses .Net&rsquo;s extensible TypeDescriptor mechanism instead of reflection, which can be seen as a mechanism similar to reflection, only the type information can be extended runtime. I had to extend the type information once for each entity that get&rsquo;s generated, with the information in the buddy metadata class. I can&rsquo;t do this in the constructor, because then it would happen way to often. A static constructor seemed a good place to me. The declaration of the static constructor is found on line 31. On line 36 I use the MethodBase class to get a reference to the current type. This is needed since I need to extend the type information of the current type. On line 37-39 I check whether the MetadataTypeAttribute is present. If so, I retrieve it on line 41. This attribute has the property “MetadataClassType&rdquo;. This property contains the type you supply when you place the MetadataType attribute. On line 42 I extend the type information of the current type with the associated metadata. I do this by calling the AddProviderTransparent method on the TypeDescriptor class and supplying an AssociatedMetadataTypeTypeDescriptionProvider. This provider is used for the buddy class mechanism and it&rsquo;s constructor takes two arguments. First is the type you wish to extend with extra metadata, second is the metadata class.

    So know we have a T4 template, that generates entities with support for WPF validation using data annotations and the buddy mechanism. Of course you can use the generated entities for any technology that doesn&rsquo;t use the data annotations by default.

    The WPF Application

    The article is getting quite long, so bare with me :). Luckily, this paragraph will be short. Here is the XAML:

       1: <Window x:Class="ProductsApp.MainWindow"

       2:         >="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       3:         >="http://schemas.microsoft.com/winfx/2006/xaml"

       4:         Title="MainWindow" Height="350" Width="525" mc:Ignorable="d" >="http://schemas.microsoft.com/expression/blend/2008" >="http://schemas.openxmlformats.org/markup-compatibility/2006" >="clr-namespace:EntitiesLibrary;assembly=EntitiesLibrary" >

       5:       <Grid >

       6:         <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" Name="_dtgProducts" RowDetailsVisibilityMode="VisibleWhenSelected">

       7:             <DataGrid.Columns>

       8:                 <DataGridTextColumn x:Name="classColumn" Binding="{Binding Path=Class}" Header="Class" Width="SizeToHeader" />

       9:                 <DataGridTextColumn x:Name="colorColumn" Binding="{Binding Path=Color}" Header="Color" Width="SizeToHeader" />

      10:                 <DataGridTextColumn x:Name="daysToManufactureColumn" Binding="{Binding Path=DaysToManufacture}" Header="Days To Manufacture" Width="SizeToHeader" />

      11:                 <DataGridTemplateColumn x:Name="discontinuedDateColumn" Header="Discontinued Date" Width="SizeToHeader">

      12:                     <DataGridTemplateColumn.CellTemplate>

      13:                         <DataTemplate>

      14:                             <DatePicker SelectedDate="{Binding Path=DiscontinuedDate, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />

      15:                         </DataTemplate>

      16:                     </DataGridTemplateColumn.CellTemplate>

      17:                 </DataGridTemplateColumn>

      18:                 <DataGridTextColumn x:Name="errorColumn" Binding="{Binding Path=Error}" Header="Error" IsReadOnly="True" Width="SizeToHeader" />

      19:                 <DataGridCheckBoxColumn x:Name="finishedGoodsFlagColumn" Binding="{Binding Path=FinishedGoodsFlag}" Header="Finished Goods Flag" Width="SizeToHeader" />

      20:                 <DataGridTextColumn x:Name="listPriceColumn" Binding="{Binding Path=ListPrice,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}" Header="List Price" Width="SizeToHeader" />

      21:                 <DataGridCheckBoxColumn x:Name="makeFlagColumn" Binding="{Binding Path=MakeFlag}" Header="Make Flag" Width="SizeToHeader" />

      22:                 <DataGridTemplateColumn x:Name="modifiedDateColumn" Header="Modified Date" Width="SizeToHeader">

      23:                     <DataGridTemplateColumn.CellTemplate>

      24:                         <DataTemplate>

      25:                             <DatePicker SelectedDate="{Binding Path=ModifiedDate, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />

      26:                         </DataTemplate>

      27:                     </DataGridTemplateColumn.CellTemplate>

      28:                 </DataGridTemplateColumn>

      29:                 <DataGridTextColumn x:Name="nameColumn" Binding="{Binding Path=Name}" Header="Name" Width="SizeToHeader" />

      30:                 <DataGridTextColumn x:Name="productIDColumn" Binding="{Binding Path=ProductID}" Header="Product ID" Width="SizeToHeader" />

      31:                 <DataGridTextColumn x:Name="productLineColumn" Binding="{Binding Path=ProductLine}" Header="Product Line" Width="SizeToHeader" />

      32:                 <DataGridTextColumn x:Name="productModelIDColumn" Binding="{Binding Path=ProductModelID}" Header="Product Model ID" Width="SizeToHeader" />

      33:                 <DataGridTextColumn x:Name="productNumberColumn" Binding="{Binding Path=ProductNumber}" Header="Product Number" Width="SizeToHeader" />

      34:                 <DataGridTextColumn x:Name="productSubcategoryIDColumn" Binding="{Binding Path=ProductSubcategoryID}" Header="Product Subcategory ID" Width="SizeToHeader" />

      35:                 <DataGridTextColumn x:Name="reorderPointColumn" Binding="{Binding Path=ReorderPoint}" Header="Reorder Point" Width="SizeToHeader" />

      36:                 <DataGridTextColumn x:Name="rowguidColumn" Binding="{Binding Path=rowguid}" Header="rowguid" Width="SizeToHeader" />

      37:                 <DataGridTextColumn x:Name="safetyStockLevelColumn" Binding="{Binding Path=SafetyStockLevel}" Header="Safety Stock Level" Width="SizeToHeader" />

      38:                 <DataGridTemplateColumn x:Name="sellEndDateColumn" Header="Sell End Date" Width="SizeToHeader">

      39:                     <DataGridTemplateColumn.CellTemplate>

      40:                         <DataTemplate>

      41:                             <DatePicker SelectedDate="{Binding Path=SellEndDate, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />

      42:                         </DataTemplate>

      43:                     </DataGridTemplateColumn.CellTemplate>

      44:                 </DataGridTemplateColumn>

      45:                 <DataGridTemplateColumn x:Name="sellStartDateColumn" Header="Sell Start Date" Width="SizeToHeader">

      46:                     <DataGridTemplateColumn.CellTemplate>

      47:                         <DataTemplate>

      48:                             <DatePicker SelectedDate="{Binding Path=SellStartDate, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />

      49:                         </DataTemplate>

      50:                     </DataGridTemplateColumn.CellTemplate>

      51:                 </DataGridTemplateColumn>

      52:                 <DataGridTextColumn x:Name="sizeColumn" Binding="{Binding Path=Size}" Header="Size" Width="SizeToHeader" />

      53:                 <DataGridTextColumn x:Name="sizeUnitMeasureCodeColumn" Binding="{Binding Path=SizeUnitMeasureCode}" Header="Size Unit Measure Code" Width="SizeToHeader" />

      54:                 <DataGridTextColumn x:Name="standardCostColumn" Binding="{Binding Path=StandardCost}" Header="Standard Cost" Width="SizeToHeader" />

      55:                 <DataGridTextColumn x:Name="styleColumn" Binding="{Binding Path=Style}" Header="Style" Width="SizeToHeader" />

      56:                 <DataGridTextColumn x:Name="weightColumn" Binding="{Binding Path=Weight}" Header="Weight" Width="SizeToHeader" />

      57:                 <DataGridTextColumn x:Name="weightUnitMeasureCodeColumn" Binding="{Binding Path=WeightUnitMeasureCode}" Header="Weight Unit Measure Code" Width="SizeToHeader" />

      58:             </DataGrid.Columns>

      59:         </DataGrid>

      60:     </Grid>

      61: </Window>

    The most important part is on line 20. The binding for the ListPrice property is set to TwoWay, ValidatesOnDataErrors is true and NotifyOnValidationError is also true. This will make sure that when the user enters a listprice smaller than zero, validation will kick in:

    image

    &nbsp;

    &nbsp;

    The WCF Service

    Last up is the WCF Service, which uses the new EF 4.1 DbContext api to load from- and&nbsp;update data in the database:

       1: using System;

       2: using System.Linq;

       3: using System.Runtime.Serialization;

       4: using System.ServiceModel;

       5: using System.ServiceModel.Activation;

       6: using EntitiesLibrary;

       7: using DbContextLibrary;

       8: using System.Collections.Generic;

       9: using System.Data.Entity.Validation;

      10: using System.Data;

      11:&nbsp; 

      12: namespace ProductsApp.Web

      13: {

      14:     [ServiceContract(Namespace = "")]

      15:     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

      16:     public class ProductService

      17:     {

      18:         [OperationContract]

      19:         public Product[] GetProducts() 

      20:         {

      21:             using (AdventureWorks2008Entities ents = new AdventureWorks2008Entities())

      22:             {

      23:                 return ents.Product.AsNoTracking().ToArray();

      24:             }

      25:         }

      26:&nbsp; 

      27:         [OperationContract]

      28:         [FaultContract(typeof(string[]))]

      29:         public void UpdateProduct(Product toUpdate)

      30:         {

      31:             using (AdventureWorks2008Entities ents = new AdventureWorks2008Entities())

      32:             {

      33:                 //prevent validating before save because I do this manually

      34:                 ents.Configuration.ValidateOnSaveEnabled = false;

      35:&nbsp; 

      36:                 //Prevent detecting changes before save....

      37:                 ents.Configuration.AutoDetectChangesEnabled = false;

      38:&nbsp; 

      39:                 ents.Product.Attach(toUpdate);

      40:                 ents.ChangeTracker.Entries<Product>().

      41:                         Single((entry) => entry.Entity == toUpdate).State = EntityState.Modified;

      42:                 DbEntityValidationResult error = ents.GetValidationErrors().SingleOrDefault();

      43:                 if (error != null)

      44:                 {

      45:                     throw new FaultException<string[]>(

      46:                             error.ValidationErrors.Select((er) => er.PropertyName + ": " + er.ErrorMessage).ToArray()

      47:                         );

      48:                 }

      49:                 else

      50:                 {

      51:                     

      52:                     ents.SaveChanges();

      53:                 }

      54:             }

      55:         }

      56:     }

      57: }

    • Line 19-25: This isn&rsquo;t that exciting, I just load the data with no tracking from the database. In a service, tracking won&rsquo;t do you any good.
    • Line 29-55: This is the most interesting part. My web project also references my EntitiesLibrary project, witch contains the Product class with the buddy mechanism and the data annotation on the listprice. The DbContext api will by default automatically validate before saving and will throw an exception if there are errors. I&rsquo;m disabling this behavior on line 34 since I&rsquo;m going to validate up front. Because I&rsquo;m in a service, the DbContext can&rsquo;t detect any changes since it doesn&rsquo;t have any original values, so I&rsquo;m also disabling this behavior on line 37. On line 39 the product is attached. On line 40-41 I&rsquo;m changing the state of the product to modified. This is necessary, if you forget this, the DbContext won&rsquo;t validate (even when doing this manually) or save the product. Since I know for certain that the DbContext is only tracking one entity, I retrieve the single DbEntityValidationResult on line 42. If there are no errors, I will get an empty collection and because of the SingleOrDefault method I will get null. Thus, if the error is not null on line 43, I throw a new FaultException and supply it with a string[]. This string[] contains the validation errors for all the properties that caused errors. Of course, this will only be the listprice since it&rsquo;s the only one with a data annotation. If the validation returned null, I&rsquo;m saving the changes on line 52.

    The Console Application

    As you can see above, using the DbContext api means that you don&rsquo;t have to write validation code on both the client and the server. This problem was already solved for Silverlight by WCF RIA Services, but now we can finally share the data annotations between the server and other client technologies as well. Now let&rsquo;s test our service with a client that doesn&rsquo;t do any validation in the UI and tries to submit some illegal values:

       1: using System;

       2: using System.Collections.Generic;

       3: using System.Linq;

       4: using System.Text;

       5: using ProductsConsoleApp.Services;

       6: using EntitiesLibrary;

       7: using System.ServiceModel;

       8: using System.ComponentModel;

       9:&nbsp; 

      10: namespace ProductsConsoleApp

      11: {

      12:     class Program

      13:     {

      14:         static void Main(string[] args)

      15:         {

      16:             ProductServiceClient client = new ProductServiceClient();

      17:             Product first = client.GetProducts()[0];

      18:             first.ListPrice = -1;

      19:             try

      20:             {

      21:                 client.UpdateProduct(first);

      22:             }

      23:             catch (FaultException<string[]> ex)

      24:             {

      25:                 Console.WriteLine("There were validation errors on the server:");

      26:                 foreach (string errorMessage in ex.Detail)

      27:                 {

      28:                     Console.WriteLine(errorMessage);

      29:                 }

      30:             }

      31:             Console.ReadKey();

      32:         }

      33:     }

      34: }

    If you&rsquo;d run the application above you would get the following output:

    image

    And we can see that our server is protected against bad input.

    Conclusion

    One of the most overlooked features of the Entity Framework 4.1 is the new ability to validate the entities according to the buddy class mechanism and data annotations. This means that when we write serverside code, we don&rsquo;t have to write the validation logic ourselves and that we can share the entities between client and server to use the same validation logic on the client. This also works of course when the client is an ASP.NET MVC Controller for example.

    I&rsquo;ve tried to share code this way between a Silverlight client and a WCF Service but this doesn&rsquo;t work. This is because the EntitiesLibrary project must be a Silverlight class library before it can be referenced by both Silverlight and the server project. In Silverlight the data annotations are defined in a different assembly with a different version than the data annotations in the full .Net framework. The Entity Framework and the Validator class in the full .Net framework will NOT recognize data annotations from Silverlight. It seems that the best way to share validation code with Silverlight is by using WCF RIA Services. You can find the working example here.

    Share this

Alex van Beek

View profile

IT Training at Info Support

Which training fits you?

Consultancy

Consultancy

Related blogs

  • Video Conferencing en OBS Studio koppelen: online prese…

    Video Conferencing en OBS Studio koppelen: online prese… Maaike Brouwer - 1 year ago

  • Verantwoordelijkheid pakken in jouw eigen persoonlijke …

    Verantwoordelijkheid pakken in jouw eigen persoonlijke … Stephan Versteegh - 1 year ago

  • Tips voor als je gaat afstuderen

    Tips voor als je gaat afstuderen Bart Renders - 2 years ago

Related downloads

  • Beslisboom voor een rechtmatig ‘kopietje productie’

  • Klantreferentie: Remmicom zet wetgeving om in intellige…

  • Klantreferentie RDW: Samenwerken voor veilig en vertrou…

  • Klantreferentie BeFrank: Strategische IT voor een innov…

  • Wie durft te experimenteren met data in de zorg?

Related videos

  • mijnverzekeringenopeenrij.nl

    mijnverzekeringenopeenrij.nl

  • Winnaar | Innovation Projects 2017

    Winnaar | Innovation Projects 2017

  • Explore | Info Support & HAN & Poliskluis

    Explore | Info Support & HAN & Poliskluis

  • LifeApps bij HagaZiekenhuis

    LifeApps bij HagaZiekenhuis

  • Info Support | Bedrijfsfilm

    Info Support | Bedrijfsfilm

Nieuwsbrief

* verplichte velden

Contact

  • Head office NL
  • Kruisboog 42
  • 3905 TG Veenendaal
  • T +31 318 552020
  • Call
  • Mail
  • Directions
  • Head office BE
  • Generaal De Wittelaan 17
  • bus 30 2800 Mechelen
  • T +32 15 286370
  • Call
  • Mail
  • Directions

Follow us

  • Twitter
  • Facebook
  • Linkedin
  • Youtube

Newsletter

Sign in

Extra

  • Media Library
  • Disclaimer
  • Algemene voorwaarden
  • ISHBS Webmail
  • Extranet
Beheer cookie toestemming
Deze website maakt gebruik van Functionele en Analytische cookies voor website optimalisatie en statistieken.
Functioneel
Altijd actief
De technische opslag of toegang is strikt noodzakelijk voor het legitieme doel het gebruik mogelijk te maken van een specifieke dienst waarom de abonnee of gebruiker uitdrukkelijk heeft gevraagd, of met als enig doel de uitvoering van de transmissie van een communicatie over een elektronisch communicatienetwerk.
Voorkeuren
De technische opslag of toegang is noodzakelijk voor het legitieme doel voorkeuren op te slaan die niet door de abonnee of gebruiker zijn aangevraagd.
Statistieken
De technische opslag of toegang die uitsluitend voor statistische doeleinden wordt gebruikt. De technische opslag of toegang die uitsluitend wordt gebruikt voor anonieme statistische doeleinden. Zonder dagvaarding, vrijwillige naleving door uw Internet Service Provider, of aanvullende gegevens van een derde partij, kan informatie die alleen voor dit doel wordt opgeslagen of opgehaald gewoonlijk niet worden gebruikt om je te identificeren.
Marketing
De technische opslag of toegang is nodig om gebruikersprofielen op te stellen voor het verzenden van reclame, of om de gebruiker op een website of over verschillende websites te volgen voor soortgelijke marketingdoeleinden.
Beheer opties Beheer diensten Beheer leveranciers Lees meer over deze doeleinden
Voorkeuren
{title} {title} {title}