diff --git a/.gitignore b/.gitignore index 256b66e..9128333 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ + *.user *.obj *.lib @@ -5,7 +6,6 @@ *.ncb *.cache *.gem -[Ll]ib [Oo]bj [Bb]in [Bb]uild @@ -14,4 +14,7 @@ _ReSharper*/ *.vs10x Source/Gallio Reports -Source/TestResults \ No newline at end of file +Source/TestResults +Source/_UpgradeReport_Files/ +*.orig +Source/UpgradeLog* \ No newline at end of file diff --git a/Source/Debug.xunit b/Source/Debug.xunit new file mode 100644 index 0000000..f6a167c --- /dev/null +++ b/Source/Debug.xunit @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper.Specs/FluentMetadata.AutoMapper.Specs.csproj b/Source/FluentMetadata.AutoMapper.Specs/FluentMetadata.AutoMapper.Specs.csproj new file mode 100644 index 0000000..22f824b --- /dev/null +++ b/Source/FluentMetadata.AutoMapper.Specs/FluentMetadata.AutoMapper.Specs.csproj @@ -0,0 +1,75 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {36BC8904-D8F8-4778-A5BB-F27F1346BAE9} + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + FluentMetadata.AutoMapper.Specs + FluentMetadata.AutoMapper.Specs + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\AutoMapper.1.1.0.118\lib\AutoMapper.dll + + + + + + + + + False + ..\..\external\xUnit\xunit.dll + + + + + + + + + + {3EA6A0AC-8377-4C2F-A0EA-61278245620E} + FluentMetadata.AutoMapper + + + {C73F37FA-D859-4714-8335-35069322176A} + FluentMetadata.Core + + + + + + + + \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper.Specs/Properties/AssemblyInfo.cs b/Source/FluentMetadata.AutoMapper.Specs/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3a174f7 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper.Specs/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FluentMetadata.AutoMapper.Specs")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("FluentMetadata.AutoMapper.Specs")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("10e83014-1379-41a6-821d-50d5a1317676")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/FluentMetadata.AutoMapper.Specs/SystemUnderTest.cs b/Source/FluentMetadata.AutoMapper.Specs/SystemUnderTest.cs new file mode 100644 index 0000000..2a5ebe5 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper.Specs/SystemUnderTest.cs @@ -0,0 +1,64 @@ +using AutoMapper; +namespace FluentMetadata.AutoMapper.Specs +{ + class Source + { + public string MyProperty { get; set; } + public int Named { get; set; } + public Nested Nested { get; set; } + public string StringField { get; set; } + } + + class Nested + { + public FurtherNested FurtherNested { get; set; } + } + + class FurtherNested + { + public int Id { get; set; } + } + + class Destination + { + public string MyProperty { get; set; } + public int Renamed { get; set; } + public int NestedFurtherNestedId { get; set; } + public int IntProperty { get; set; } + } + + class FakeResolver : ValueResolver + { + protected override int ResolveCore(string source) + { + return default(int); + } + } + + class SourceMetaData : ClassMetadata + { + public SourceMetaData() + { + Class.Display.Name("rzjsfghgafsdfh"); + Property(x => x.MyProperty).Display.Name("pockänsdfsdf"); + Property(x => x.Named).Description("adföoiulkanhsda"); + Property(x => x.StringField).UIHint("üoicvnqwnb"); + } + } + + class FurtherNestedMetaData : ClassMetadata + { + public FurtherNestedMetaData() + { + Property(x => x.Id).Is.Required(); + } + } + + class TargetMetaData : ClassMetadata + { + public TargetMetaData() + { + this.CopyAutoMappedMetadataFrom(typeof(Source)); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper.Specs/When_copying_metadata_from_an_AutoMapped_Type.cs b/Source/FluentMetadata.AutoMapper.Specs/When_copying_metadata_from_an_AutoMapped_Type.cs new file mode 100644 index 0000000..101fba2 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper.Specs/When_copying_metadata_from_an_AutoMapped_Type.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using AutoMapper; +using Xunit; + +namespace FluentMetadata.AutoMapper.Specs +{ + public class When_copying_metadata_from_an_AutoMapped_Type : IDisposable + { + Metadata destinationMetadata, + destinationMyPropertyMetadata, + destinationRenamedMetadata, + destinationNestedFurtherNestedIdMetadata, + destinationIntPropertyMetadata; + + public When_copying_metadata_from_an_AutoMapped_Type() + { + Mapper.CreateMap() + .ForMember(d => d.Renamed, o => o.MapFrom(s => s.Named)) + .ForMember(d => d.IntProperty, o => o.ResolveUsing().FromMember(s => s.StringField)); + Mapper.AssertConfigurationIsValid(); + + FluentMetadataBuilder.ForAssemblyOfType(); + + var query = new QueryFluentMetadata(); + destinationMetadata = query.GetMetadataFor(typeof(Destination)); + destinationMyPropertyMetadata = destinationMetadata.Properties + .Single(m => m.ModelName == "MyProperty"); + destinationRenamedMetadata = destinationMetadata.Properties + .Single(m => m.ModelName == "Renamed"); + destinationNestedFurtherNestedIdMetadata = destinationMetadata.Properties + .Single(m => m.ModelName == "NestedFurtherNestedId"); + destinationIntPropertyMetadata = destinationMetadata.Properties + .Single(m => m.ModelName == "IntProperty"); + } + + [Fact] + public void a_destination_property_should_have_metadata_from_the_source_property_it_is_mapped_to() + { + Assert.Equal("pockänsdfsdf", destinationMyPropertyMetadata.GetDisplayName()); + } + + [Fact] + public void the_destination_type_should_have_metadata_from_the_source_type_it_is_mapped_to() + { + Assert.Equal("rzjsfghgafsdfh", destinationMetadata.GetDisplayName()); + } + + //TODO [on AutoMapper update] check if AutoMapper makes projected source property accessible + [Fact(Skip = "unsupported until AutoMapper makes projected source property accessible")] + public void a_projected_destination_property_should_have_metadata_from_the_source_property_it_is_mapped_to() + { + Assert.Equal("adföoiulkanhsda", destinationRenamedMetadata.Description); + } + + [Fact] + public void a_flattened_destination_property_should_have_metadata_from_the_source_property_it_is_mapped_to() + { + Assert.Equal(true, destinationNestedFurtherNestedIdMetadata.Required); + } + + //TODO [on AutoMapper update] check if AutoMapper makes source property that destination property is resolved from accessible + [Fact(Skip = "unsupported until AutoMapper makes the source property the destination property is resolved from accessible")] + public void a_destination_property_resolved_from_a_source_property_should_have_metadata_from_the_source_property() + { + Assert.Equal("üoicvnqwnb", destinationIntPropertyMetadata.TemplateHint); + } + + public void Dispose() + { + FluentMetadataBuilder.Reset(); + Mapper.Reset(); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper.Specs/packages.config b/Source/FluentMetadata.AutoMapper.Specs/packages.config new file mode 100644 index 0000000..8395173 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper.Specs/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper/AutoMapperHelper.cs b/Source/FluentMetadata.AutoMapper/AutoMapperHelper.cs new file mode 100644 index 0000000..e5b0594 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper/AutoMapperHelper.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using AutoMapper; + +namespace FluentMetadata.AutoMapper +{ + internal class AutoMapperHelper + { + /// + /// Gets the mapped members for a source/destination type pair. + /// + /// The source Type. + /// The destination Type. + /// + internal static IEnumerable GetMemberMapsOf(Type source, Type destination) + { + var maps = new Collection(); + + foreach (var propertyMap in GetRelevantMappedMembersOf(source, destination)) + { + // get the source property of the PropertyMap + var sourceValueResolvers = propertyMap.GetSourceValueResolvers() + // just plain property maps, there's no interesting metadata on a custom mapping function + .OfType(); + var sourceProperty = sourceValueResolvers + // "There can be only one" + .SingleOrDefault(svr => svr.MemberType == propertyMap.DestinationProperty.MemberType); + + if (sourceProperty != null) + { + maps.Add(new MemberMap + { + SourceName = sourceValueResolvers.Aggregate(string.Empty, (result, svr) => result + svr.Name), + DestinationName = propertyMap.DestinationProperty.Name + }); + } + } + + return maps; + } + + /// + /// Gets the mapped members for a source/destination type pair + /// leaving out mapped members that are irrelevant. + /// + /// The source type. + /// The destination type. + /// + static IEnumerable GetRelevantMappedMembersOf(Type source, Type destination) + { + var typeMap = Mapper.FindTypeMapFor(source, destination); + return + typeMap != null ? + // filter by non-ignored PropertyMaps + typeMap.GetPropertyMaps().Where(m => m.IsIgnored() == false) : + Enumerable.Empty(); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper/ClassMetadataAutoMapperExtensions.cs b/Source/FluentMetadata.AutoMapper/ClassMetadataAutoMapperExtensions.cs new file mode 100644 index 0000000..81d6231 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper/ClassMetadataAutoMapperExtensions.cs @@ -0,0 +1,27 @@ +using System; + +namespace FluentMetadata.AutoMapper +{ + /// + /// AutoMapper extensions to + /// + public static class ClassMetadataAutoMapperExtensions + { + /// + /// Copies the the source type's metadata to the destination type's metadata + /// using the mapping information provided by AutoMapper. + /// + /// The type of the destination. + /// The destination metadata. + /// The source type. + public static void CopyAutoMappedMetadataFrom(this ClassMetadata to, Type from) + { + var destinationType = typeof(TDestination); + MetadataHelper.CopyMappedMetadata( + from, + destinationType, + AutoMapperHelper.GetMemberMapsOf(from, destinationType) + ); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper/FluentMetadata.AutoMapper.csproj b/Source/FluentMetadata.AutoMapper/FluentMetadata.AutoMapper.csproj new file mode 100644 index 0000000..da3d943 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper/FluentMetadata.AutoMapper.csproj @@ -0,0 +1,65 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {3EA6A0AC-8377-4C2F-A0EA-61278245620E} + Library + Properties + FluentMetadata.AutoMapper + FluentMetadata.AutoMapper + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\AutoMapper.1.1.0.118\lib\AutoMapper.dll + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + {C73F37FA-D859-4714-8335-35069322176A} + FluentMetadata.Core + + + + + \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper/Properties/AssemblyInfo.cs b/Source/FluentMetadata.AutoMapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5b6a4b9 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FluentMetadata.AutoMapper")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8e80ca63-7b53-49c9-b5e5-a708c8f3de7e")] \ No newline at end of file diff --git a/Source/FluentMetadata.AutoMapper/packages.config b/Source/FluentMetadata.AutoMapper/packages.config new file mode 100644 index 0000000..8395173 --- /dev/null +++ b/Source/FluentMetadata.AutoMapper/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/Builder/DisplayBuilderTests.cs b/Source/FluentMetadata.Core.Specs/Builder/DisplayBuilderTests.cs index 65dc3cf..ea3abee 100644 --- a/Source/FluentMetadata.Core.Specs/Builder/DisplayBuilderTests.cs +++ b/Source/FluentMetadata.Core.Specs/Builder/DisplayBuilderTests.cs @@ -29,7 +29,7 @@ public void DisplayBuilder_Ctor_Format_IsNull() [Fact] public void DisplayBuilder_Ctor_Name_IsNull() { - Assert.Null(metadata.DisplayName); + Assert.Null(metadata.GetDisplayName()); } [Fact] @@ -37,7 +37,7 @@ public void DisplayBuilder_NullText_NullText_IsValue() { builder.NullText("TheNullText"); Assert.Equal("TheNullText", metadata.NullDisplayText); - Assert.Null(metadata.DisplayName); + Assert.Null(metadata.GetDisplayName()); Assert.Null(metadata.DisplayFormat); } @@ -45,7 +45,17 @@ public void DisplayBuilder_NullText_NullText_IsValue() public void DisplayBuilder_Name_Name_IsValue() { builder.Name("TheNameText"); - Assert.Equal("TheNameText", metadata.DisplayName); + Assert.Equal("TheNameText", metadata.GetDisplayName()); + Assert.Null(metadata.NullDisplayText); + Assert.Null(metadata.DisplayFormat); + } + + [Fact] + public void DisplayBuilder_Name_Function_Equals_Metadata_DisplayName() + { + const string displayName = "asdf"; + builder.Name(() => displayName); + Assert.Equal(displayName, metadata.GetDisplayName()); Assert.Null(metadata.NullDisplayText); Assert.Null(metadata.DisplayFormat); } @@ -56,7 +66,7 @@ public void DisplayBuilder_Format_Format_IsValue() builder.Format("TheFormatText"); Assert.Equal("TheFormatText", metadata.DisplayFormat); Assert.Null(metadata.NullDisplayText); - Assert.Null(metadata.DisplayName); + Assert.Null(metadata.GetDisplayName()); } [Fact] @@ -66,9 +76,8 @@ public void DisplayBuilder_Allset_IsValue() builder.Name("TheNameText"); builder.NullText("TheNullText"); Assert.Equal("TheFormatText", metadata.DisplayFormat); - Assert.Equal("TheNameText", metadata.DisplayName); + Assert.Equal("TheNameText", metadata.GetDisplayName()); Assert.Equal("TheNullText", metadata.NullDisplayText); } - } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/ClassMetadata_with_Person.cs b/Source/FluentMetadata.Core.Specs/ClassMetadata_with_Person.cs index 5725708..0410bbe 100644 --- a/Source/FluentMetadata.Core.Specs/ClassMetadata_with_Person.cs +++ b/Source/FluentMetadata.Core.Specs/ClassMetadata_with_Person.cs @@ -1,3 +1,5 @@ +using System.Linq; +using FluentMetadata.Rules; using FluentMetadata.Specs.SampleClasses; using Xunit; @@ -10,26 +12,41 @@ public class ClassMetadata_with_Person : MetadataTestBase public ClassMetadata_with_Person() { var query = new QueryFluentMetadata(); - classMetadata = query.GetMetadataFor(typeof (Person)); + classMetadata = query.GetMetadataFor(typeof(Person)); } [Fact] public void Metadata_ModelType_is_Person() { - Assert.Equal(typeof (Person), classMetadata.ModelType); + Assert.Equal(typeof(Person), classMetadata.ModelType); } [Fact] public void Metadata_ModelName_is_Null() { - Assert.Null(classMetadata.ModelName); -// Assert.Equal("Person", classMetadata.ModelName); + Assert.Null(classMetadata.ModelName); } [Fact] public void Metadata_Display_is_Benutzer() { - Assert.Equal("Benutzer", classMetadata.DisplayName); + Assert.Equal("Benutzer", classMetadata.GetDisplayName()); + } + + [Fact] + public void Instance_with_FirstName_different_from_LastName_is_invalid() + { + var rule = classMetadata.Rules.OfType>().Last(); + var person = new Person { FirstName = "foo", LastName = "bar" }; + Assert.False(rule.IsValid(person)); + } + + [Fact] + public void Instance_with_FirstName_equal_to_LastName_is_valid() + { + var rule = classMetadata.Rules.OfType>().Last(); + var person = new Person { FirstName = "foo", LastName = "foo" }; + Assert.True(rule.IsValid(person)); } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/ClassMetadata_with_WebUser.cs b/Source/FluentMetadata.Core.Specs/ClassMetadata_with_WebUser.cs index dba101d..367f30e 100644 --- a/Source/FluentMetadata.Core.Specs/ClassMetadata_with_WebUser.cs +++ b/Source/FluentMetadata.Core.Specs/ClassMetadata_with_WebUser.cs @@ -1,3 +1,6 @@ +using System; +using System.Linq; +using FluentMetadata.Rules; using FluentMetadata.Specs.SampleClasses; using Xunit; @@ -28,8 +31,36 @@ public void ModeType_is_WebUser() [Fact] public void DisplayName_is_Benutzer() { - Assert.Equal("Benutzer", classMetadata.DisplayName); + Assert.Equal("Benutzer", classMetadata.GetDisplayName()); } + [Fact] + public void Generic_name_rule_is_valid_when_Username_is_not_equal_to_AutorName() + { + var nameRule = classMetadata.Rules + .OfType>() + .Single(); + + var webUser = new WebUser(); + webUser.Username = "Holger"; + webUser.Autor = new Autor { Name = "Albert" }; + + Assert.True(nameRule.IsValid(webUser)); + } + + [Fact] + public void Generic_name_rule_is_invalid_when_Username_is_equal_to_AutorName() + { + var nameRule = classMetadata.Rules + .OfType>() + .Single(); + + var webUser = new WebUser(); + webUser.Username = "Holger"; + webUser.Autor = new Autor { Name = "Holger" }; + + Console.WriteLine(nameRule.FormatErrorMessage(classMetadata.GetDisplayName())); + Assert.False(nameRule.IsValid(webUser)); + } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/ClassMetadata_with_WebUserIndexModel.cs b/Source/FluentMetadata.Core.Specs/ClassMetadata_with_WebUserIndexModel.cs index 7f888b2..aa2d717 100644 --- a/Source/FluentMetadata.Core.Specs/ClassMetadata_with_WebUserIndexModel.cs +++ b/Source/FluentMetadata.Core.Specs/ClassMetadata_with_WebUserIndexModel.cs @@ -17,7 +17,6 @@ public ClassMetadata_with_WebUserIndexModel() public void ModelName_is_Null() { Assert.Null(classMetadata.ModelName); -// Assert.Equal("WebUserIndexModel", classMetadata.ModelName); } [Fact] @@ -29,7 +28,7 @@ public void ModeType_is_WebUserIndexModel() [Fact] public void DisplayName_is_Benutzer() { - Assert.Equal("Benutzer",classMetadata.DisplayName); + Assert.Equal("Benutzer", classMetadata.GetDisplayName()); } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/FluentMetadata.Core.Specs.csproj b/Source/FluentMetadata.Core.Specs/FluentMetadata.Core.Specs.csproj index e8a0055..8745799 100644 --- a/Source/FluentMetadata.Core.Specs/FluentMetadata.Core.Specs.csproj +++ b/Source/FluentMetadata.Core.Specs/FluentMetadata.Core.Specs.csproj @@ -13,7 +13,8 @@ FluentMetadata.Core.Specs v4.0 512 - 0 + + 4.0 publish\ @@ -87,9 +88,12 @@ + + + diff --git a/Source/FluentMetadata.Core.Specs/PropertyMedata_with_Person.cs b/Source/FluentMetadata.Core.Specs/PropertyMedata_with_Person.cs index fb90c62..58569eb 100644 --- a/Source/FluentMetadata.Core.Specs/PropertyMedata_with_Person.cs +++ b/Source/FluentMetadata.Core.Specs/PropertyMedata_with_Person.cs @@ -11,8 +11,8 @@ public class PropertyMedata_with_Person : MetadataTestBase public PropertyMedata_with_Person() { var query = new QueryFluentMetadata(); - firstName = query.GetMetadataFor(typeof (Person), "FirstName"); - lastName = query.GetMetadataFor(typeof (Person), "LastName"); + firstName = query.GetMetadataFor(typeof(Person), "FirstName"); + lastName = query.GetMetadataFor(typeof(Person), "LastName"); } [Fact] @@ -24,7 +24,7 @@ public void FirstName_ModelName_is_FirstName() [Fact] public void FirstName_ModelType_is_string() { - Assert.Equal(typeof (string), firstName.ModelType); + Assert.Equal(typeof(string), firstName.ModelType); } [Fact] @@ -42,7 +42,7 @@ public void LastName_ModelName_is_LastName() [Fact] public void LastName_ModelType_is_string() { - Assert.Equal(typeof (string), lastName.ModelType); + Assert.Equal(typeof(string), lastName.ModelType); } [Fact] diff --git a/Source/FluentMetadata.Core.Specs/PropertyMedata_with_WebUser.cs b/Source/FluentMetadata.Core.Specs/PropertyMedata_with_WebUser.cs index 2dea710..5a948e0 100644 --- a/Source/FluentMetadata.Core.Specs/PropertyMedata_with_WebUser.cs +++ b/Source/FluentMetadata.Core.Specs/PropertyMedata_with_WebUser.cs @@ -1,18 +1,24 @@ -using FluentMetadata.Specs.SampleClasses; +using System; +using System.Linq; +using FluentMetadata.Rules; +using FluentMetadata.Specs.SampleClasses; using Xunit; namespace FluentMetadata.Specs { public class PropertyMedata_with_WebUser : MetadataTestBase { - private Metadata username; - private Metadata id; + Metadata lastLogin, username, id, passWordHash, role, bounceCount; public PropertyMedata_with_WebUser() { var query = new QueryFluentMetadata(); username = query.GetMetadataFor(typeof(WebUser), "Username"); id = query.GetMetadataFor(typeof(WebUser), "Id"); + lastLogin = query.GetMetadataFor(typeof(WebUser), "LastLogin"); + passWordHash = query.GetMetadataFor(typeof(WebUser), "PasswordHash"); + role = query.GetMetadataFor(typeof(WebUser), "Role"); + bounceCount = query.GetMetadataFor(typeof(WebUser), "BounceCount"); } [Fact] @@ -30,7 +36,7 @@ public void Username_ModelType_is_string() [Fact] public void Username_DisplayName_is_Benutzername() { - Assert.Equal("Benutzername", username.DisplayName); + Assert.Equal("Benutzername", username.GetDisplayName()); } [Fact] @@ -39,6 +45,42 @@ public void Username_Required_is_true() Assert.True(username.Required.Value); } + [Fact] + public void Username_MinLength_is_3() + { + Assert.Equal(3, username.GetMinimumLength()); + } + + [Fact] + public void Username_MaxLength_is_256() + { + Assert.Equal(256, username.GetMaximumLength()); + } + + [Fact] + public void PassWordHash_MinLength_is_32() + { + Assert.Equal(32, passWordHash.GetMinimumLength()); + } + + [Fact] + public void PassWordHash_MaxLength_is_null() + { + Assert.Null(passWordHash.GetMaximumLength()); + } + + [Fact] + public void Role_MinLength_is_null() + { + Assert.Null(role.GetMinimumLength()); + } + + [Fact] + public void Role_MaxLength_is_256() + { + Assert.Equal(256, role.GetMaximumLength()); + } + [Fact] public void Id_ModelName_is_Id() { @@ -56,5 +98,47 @@ public void Id_Required_is_false() { Assert.False(id.Required.HasValue); } + + [Fact] + public void Last_Login_Minimum_is_2010_1_23() + { + Assert.Equal(new DateTime(2010, 1, 23), lastLogin.GetRangeMinimum()); + } + + [Fact] + public void Last_Login_Maximum_is_DoomsDay() + { + Assert.Equal(DateTime.MaxValue, lastLogin.GetRangeMaximum()); + } + + [Fact] + public void Generic_bounceCount_rule_is_valid_when_email_has_bounced_twice() + { + var bounceCountRule = bounceCount.Rules + .OfType>() + .Single(); + var webUser = new WebUser(); + + webUser.MailHasBounced(); + webUser.MailHasBounced(); + + Assert.True(bounceCountRule.IsValid(webUser.BounceCount)); + } + + [Fact] + public void Generic_bounceCount_rule_is_invalid_when_email_has_bounced_thrice() + { + var bounceCountRule = bounceCount.Rules + .OfType>() + .Single(); + var webUser = new WebUser(); + + webUser.MailHasBounced(); + webUser.MailHasBounced(); + webUser.MailHasBounced(); + + Console.WriteLine(bounceCountRule.FormatErrorMessage(bounceCount.GetDisplayName())); + Assert.False(bounceCountRule.IsValid(webUser.BounceCount)); + } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/PropertyMedata_with_WebUserIndexModel.cs b/Source/FluentMetadata.Core.Specs/PropertyMedata_with_WebUserIndexModel.cs index 8f4c351..359dfc1 100644 --- a/Source/FluentMetadata.Core.Specs/PropertyMedata_with_WebUserIndexModel.cs +++ b/Source/FluentMetadata.Core.Specs/PropertyMedata_with_WebUserIndexModel.cs @@ -5,16 +5,16 @@ namespace FluentMetadata.Specs { public class PropertyMedata_with_WebUserIndexModel : MetadataTestBase { - private Metadata username; - private Metadata id; - private Metadata autorName; + Metadata username, id, autorName, email, role; public PropertyMedata_with_WebUserIndexModel() { var query = new QueryFluentMetadata(); username = query.GetMetadataFor(typeof(WebUserIndexModel), "Username"); id = query.GetMetadataFor(typeof(WebUserIndexModel), "Id"); + email = query.GetMetadataFor(typeof(WebUserIndexModel), "EMail"); autorName = query.GetMetadataFor(typeof(WebUserIndexModel), "AutorName"); + role = query.GetMetadataFor(typeof(WebUserIndexModel), "Role"); } [Fact] @@ -32,7 +32,7 @@ public void Username_ModelType_is_string() [Fact] public void Username_DisplayName_is_Benutzername() { - Assert.Equal("Benutzername", username.DisplayName); + Assert.Equal("Benutzername", username.GetDisplayName()); } [Fact] @@ -62,7 +62,101 @@ public void Id_Required_is_false() [Fact] public void AutorName_DisplayName_is_emaN() { - Assert.Equal("emaN",autorName.DisplayName); + Assert.Equal("emaN", autorName.GetDisplayName()); + } + + [Fact] + public void EMail_DataTypeName_is_EmailAddress() + { + Assert.Equal("EmailAddress", email.DataTypeName); + } + + [Fact] + public void Username_Description_is_Name_des_Benutzers() + { + Assert.Equal("Name des Benutzers", username.Description); + } + + [Fact] + public void EMail_DisplayFormat_is_MailtoLink() + { + Assert.Equal("{0}", email.DisplayFormat); + } + + [Fact] + public void EMail_EditorFormat_is_plain_value() + { + Assert.Equal("{0}", email.EditorFormat); + } + + [Fact] + public void Id_HideSurroundingHtml_is_true() + { + Assert.True(id.HideSurroundingHtml.HasValue); + Assert.True(id.HideSurroundingHtml.Value); + } + + [Fact] + public void Username_ReadOnly_is_true() + { + Assert.True(username.Readonly); + } + + [Fact] + public void AutorName_NullDisplayText_is_Anonymous_Autor() + { + Assert.Equal("Anonymous Autor", autorName.NullDisplayText); + } + + [Fact] + public void Id_ShowDisplay_is_false() + { + Assert.False(id.ShowDisplay); + } + + [Fact] + public void Id_ShowEditor_is_false() + { + Assert.False(id.ShowEditor); + } + + [Fact] + public void Role_TemplateHint_is_Roles() + { + Assert.Equal("Roles", role.TemplateHint); + } + + [Fact] + public void EMail_Watermark_is_dummy_address() + { + Assert.Equal("john@doe.com", email.Watermark); + } + + [Fact] + public void Username_ConvertEmptyStringToNull_is_false() + { + Assert.False(username.ConvertEmptyStringToNull); + } + + [Fact] + public void Id_Hidden_is_true() + { + Assert.True(id.Hidden.HasValue); + Assert.True(id.Hidden.Value); + } + + [Fact] + public void Username_GetMaximumLength_is_256() + { + var maxLength = username.GetMaximumLength(); + Assert.True(maxLength.HasValue); + Assert.Equal(256, maxLength); + } + + [Fact] + public void Username_ContainerType_is_WebUserIndexModel() + { + Assert.Equal(typeof(WebUserIndexModel), username.ContainerType); } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/Rules/PropertyMustBeLessThanOtherRuleSpecs.cs b/Source/FluentMetadata.Core.Specs/Rules/PropertyMustBeLessThanOtherRuleSpecs.cs new file mode 100644 index 0000000..5ba5fa4 --- /dev/null +++ b/Source/FluentMetadata.Core.Specs/Rules/PropertyMustBeLessThanOtherRuleSpecs.cs @@ -0,0 +1,69 @@ +using System; +using FluentMetadata.Rules; +using Xunit; + +namespace FluentMetadata.Specs.Rules +{ + public class If_a_date_should_be_before_another : InstanceContextSpecification> + { + protected override PropertyMustBeLessThanOtherRule CreateSut() + { + return new PropertyMustBeLessThanOtherRule(x => x.AlertDate, x => x.EventDate); + } + + protected override void Because() + { + } + + [Observation] + public void It_is_valid_if_it_is_earlier_than_the_other() + { + var model = new Reminder { EventDate = DateTime.Now.AddDays(1) }; + model.AlertDate = model.EventDate.AddHours(-2); + Sut.IsValid(model).ShouldBeTrue(); + } + + [Observation] + public void It_is_invalid_if_it_is_equal_to_the_other() + { + var model = new Reminder { EventDate = DateTime.Now.AddDays(1) }; + model.AlertDate = model.EventDate; + Sut.IsValid(model).ShouldBeFalse(); + } + + [Observation] + public void It_is_invalid_if_it_is_later_than_the_other() + { + var model = new Reminder { EventDate = DateTime.Now.AddDays(1) }; + model.AlertDate = model.EventDate.AddHours(2); + Sut.IsValid(model).ShouldBeFalse(); + } + } + + public class If_an_int_property_should_be_less_than_another : InstanceContextSpecification> + { + protected override PropertyMustBeLessThanOtherRule CreateSut() + { + return new PropertyMustBeLessThanOtherRule(x => x.AlertDayOfWeek, x => x.EventDayOfWeek); + } + + protected override void Because() + { + } + + [Observation] + public void It_is_valid_if_it_is_less_than_the_other() + { + var model = new Reminder { EventDayOfWeek = 5, AlertDayOfWeek = 3 }; + Sut.IsValid(model).ShouldBeTrue(); + } + } + + public class Reminder + { + public DateTime AlertDate { get; set; } + public DateTime EventDate { get; set; } + public int AlertDayOfWeek { get; set; } + public int EventDayOfWeek { get; set; } + } +} diff --git a/Source/FluentMetadata.Core.Specs/Rules/PropertyMustMatchRegexRuleSpecs.cs b/Source/FluentMetadata.Core.Specs/Rules/PropertyMustMatchRegexRuleSpecs.cs new file mode 100644 index 0000000..deef4e2 --- /dev/null +++ b/Source/FluentMetadata.Core.Specs/Rules/PropertyMustMatchRegexRuleSpecs.cs @@ -0,0 +1,83 @@ +using FluentMetadata.Rules; +using Xunit; + +namespace FluentMetadata.Specs.Rules +{ + [Concern(typeof(PropertyMustMatchRegexRule))] + public class When_property_value_should_match_a_regex : InstanceContextSpecification + { + protected override void Because() + { + } + + protected override PropertyMustMatchRegexRule CreateSut() + { + //from http://regexlib.com/REDetails.aspx?regexp_id=96 + const string validUri = @"(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?"; + return new PropertyMustMatchRegexRule(validUri); + } + + [Observation] + public void A_null_value_is_valid() // because to check this is the responsibility of the RequiredRule + { + Sut.IsValid(null).ShouldBeTrue(); + } + + [Observation] + public void A_value_matching_the_pattern_is_valid() + { + Sut.IsValid("http://regexlib.com/REDetails.aspx?regexp_id=96").ShouldBeTrue(); + } + + [Observation] + public void A_value_not_matching_the_pattern_is_invalid() + { + Sut.IsValid("regexlib.com/REDetails.aspx?regexp_id=96").ShouldBeFalse(); + } + + [Observation] + public void An_empty_string_value_is_valid() // because to check this is not the responsibility of the PropertyMustMatchRegexRule + { + Sut.IsValid(string.Empty).ShouldBeTrue(); + } + } + + [Concern(typeof(PropertyMustNotMatchRegexRule))] + public class When_property_value_should_not_match_a_regex : InstanceContextSpecification + { + protected override void Because() + { + } + + protected override PropertyMustNotMatchRegexRule CreateSut() + { + //from http://regexlib.com/REDetails.aspx?regexp_id=96 + const string validUri = @"(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?"; + return new PropertyMustNotMatchRegexRule(validUri); + } + + [Observation] + public void A_null_value_is_valid() // because to check this is the responsibility of the RequiredRule + { + Sut.IsValid(null).ShouldBeTrue(); + } + + [Observation] + public void A_value_matching_the_pattern_is_valid() + { + Sut.IsValid("http://regexlib.com/REDetails.aspx?regexp_id=96").ShouldBeFalse(); + } + + [Observation] + public void A_value_not_matching_the_pattern_is_invalid() + { + Sut.IsValid("regexlib.com/REDetails.aspx?regexp_id=96").ShouldBeTrue(); + } + + [Observation] + public void An_empty_string_value_is_valid() // because to check this is not the responsibility of the PropertyMustMatchRegexRule + { + Sut.IsValid(string.Empty).ShouldBeTrue(); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/Rules/PropertyMustMatchRuleSpecs.cs b/Source/FluentMetadata.Core.Specs/Rules/PropertyMustMatchRuleSpecs.cs new file mode 100644 index 0000000..93d9499 --- /dev/null +++ b/Source/FluentMetadata.Core.Specs/Rules/PropertyMustMatchRuleSpecs.cs @@ -0,0 +1,38 @@ +using FluentMetadata.Rules; +using Xunit; + +namespace FluentMetadata.Specs.Rules +{ + public class ChangePasswordModel + { + public string OldPassword { get; set; } + public string NewPassword { get; set; } + public string ConfirmPassword { get; set; } + } + + public class When_two_properties_should_be_equal : InstanceContextSpecification> + { + protected override PropertyMustMatchRule CreateSut() + { + return new PropertyMustMatchRule(x => x.NewPassword, x => x.ConfirmPassword); + } + + protected override void Because() + { + } + + [Observation] + public void Should_be_valid_if_properties_match() + { + var model = new ChangePasswordModel { NewPassword = "asdf", ConfirmPassword = "asdf" }; + Sut.IsValid(model).ShouldBeTrue(); + } + + [Observation] + public void Should_be_invalid_if_properties_do_not_match() + { + var model = new ChangePasswordModel { NewPassword = "qwer", ConfirmPassword = "asdf" }; + Sut.IsValid(model).ShouldBeFalse(); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/Rules/StringLengthRuleSpecs.cs b/Source/FluentMetadata.Core.Specs/Rules/StringLengthRuleSpecs.cs index ca13a79..e614e73 100644 --- a/Source/FluentMetadata.Core.Specs/Rules/StringLengthRuleSpecs.cs +++ b/Source/FluentMetadata.Core.Specs/Rules/StringLengthRuleSpecs.cs @@ -59,9 +59,94 @@ public void Should_Valid_with_a_string_with_length_0() [Observation] public void Should_Valid_with_a_string_is_NULL() { - string badLength = null; + string badLength = null; Sut.IsValid(badLength).ShouldBeTrue(); } + } + + public class When_the_minimal_StringLength_is_8_and_Maximal_StringLength_is_null : InstanceContextSpecification + { + protected override StringLengthRule CreateSut() + { + return new StringLengthRule(8, null); + } + + protected override void Because() + { + } + + [Observation] + public void Should_be_invalid_with_a_null_string() + { + Sut.IsValid(null).ShouldBeFalse(); + } + [Observation] + public void Should_be_invalid_with_a_string_with_length_7() + { + var value = new string('a', 7); + Sut.IsValid(value).ShouldBeFalse(); + } + + [Observation] + public void Should_be_valid_with_a_string_with_length_8() + { + var value = new string('a', 8); + Sut.IsValid(value).ShouldBeTrue(); + } + + [Observation] + public void Should_be_valid_with_a_string_with_length_4001() + { + var value = new string('a', 4001); + Sut.IsValid(value).ShouldBeTrue(); + } + } + + public class When_the_minimal_StringLength_is_5_and_Maximal_StringLength_is_250 : InstanceContextSpecification + { + protected override StringLengthRule CreateSut() + { + return new StringLengthRule(5, 250); + } + + protected override void Because() + { + } + + [Observation] + public void Should_be_invalid_with_a_string_with_length_4() + { + var value = new string('a', 4); + Sut.IsValid(value).ShouldBeFalse(); + } + + [Observation] + public void Should_be_valid_with_a_string_with_length_5() + { + var value = new string('a', 5); + Sut.IsValid(value).ShouldBeTrue(); + } + + [Observation] + public void Should_be_valid_with_a_string_with_length_249() + { + var value = new string('a', 249); + Sut.IsValid(value).ShouldBeTrue(); + } + + [Observation] + public void Should_be_valid_with_a_string_with_length_250() + { + var value = new string('a', 249); + Sut.IsValid(value).ShouldBeTrue(); + } + + [Observation] + public void Should_be_invalid_with_a_string_with_length_251() + { + var value = new string('a', 251); + Sut.IsValid(value).ShouldBeFalse(); + } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/DomainObjectMetadata.cs b/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/DomainObjectMetadata.cs index 65ed521..5084cef 100644 --- a/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/DomainObjectMetadata.cs +++ b/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/DomainObjectMetadata.cs @@ -4,9 +4,21 @@ public class DomainObjectMetadata : ClassMetadata where T : DomainObject { protected DomainObjectMetadata() { - Property(x => x.Id).Should.HiddenInput().Should.Not.ShowInEditor().Should.Not.ShowInDisplay().Is.ReadOnly(); - Property(x => x.Created).Is.Required().Should.Not.ShowInEditor().Display.Name("Angelegt").Is.ReadOnly(); - Property(x => x.Updated).Is.Required().Should.Not.ShowInEditor().Display.Name("Bearbeitet").Is.ReadOnly(); + Property(x => x.Id) + .Should.HiddenInput() + .Should.Not.ShowInEditor() + .Should.Not.ShowInDisplay() + .Is.ReadOnly(); + Property(x => x.Created) + .Is.Required() + .Should.Not.ShowInEditor() + .Display.Name("Angelegt") + .Is.ReadOnly(); + Property(x => x.Updated) + .Is.Required() + .Should.Not.ShowInEditor() + .Display.Name("Bearbeitet") + .Is.ReadOnly(); } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/PersonMetadata.cs b/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/PersonMetadata.cs index 6490b72..b3f297d 100644 --- a/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/PersonMetadata.cs +++ b/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/PersonMetadata.cs @@ -4,9 +4,12 @@ public class PersonMetadata : ClassMetadata { public PersonMetadata() { - Property(p => p.FirstName).Is.Required(); - Class.Display.Name("Benutzer"); - Class.Display.Format("{0} der Benutzer"); + Property(p => p.FirstName) + .Is.Required(); + Class + .Display.Name("Benutzer") + .Display.Format("{0} der Benutzer") + .Property(p => p.FirstName).ShouldEqual(p => p.LastName); } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/WebUserMetadata.cs b/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/WebUserMetadata.cs index 8376a6d..24e8991 100644 --- a/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/WebUserMetadata.cs +++ b/Source/FluentMetadata.Core.Specs/SampleClasses/MetaData/WebUserMetadata.cs @@ -1,28 +1,68 @@ +using System; + namespace FluentMetadata.Specs.SampleClasses.MetaData { - public class WebUserMetadata : DomainObjectMetadata + public class WebUserMetadata : DomainObjectMetadata { public WebUserMetadata() { - Property(x => x.Username).Length(256).Is.Required().Is.ReadOnly() - .Display.Name("Benutzername"); - Property(x => x.EMail).Length(128).Is.Required().As.EmailAddress() - .Display.Name("E-Mail").As.EmailAddress(); - Property(x => x.PasswordHash).Length(64).Is.Required() - .Should.Not.ShowInDisplay().Should.Not.ShowInEditor(); - Property(x => x.Role).UIHint("Roles").Length(256).Is.Required() + Property(x => x.Username) + .Length(3, 256) + .Is.Required() + .Is.ReadOnly() + .Display.Name("Benutzername") + .Description("Name des Benutzers") + .Is.Not.ConvertEmptyStringToNull(); + Property(x => x.EMail) + .Length(128) + .Is.Required() + .As.EmailAddress() + .Display.Name("E-Mail") + .Display.Format("{0}") + .Editor.Format("{0}") + .Editor.Watermark("john@doe.com"); + Property(x => x.PasswordHash) + .Length(32, null) + .Is.Required() + .Should.Not.ShowInDisplay() + .Should.Not.ShowInEditor(); + Property(x => x.Role) + .UIHint("Roles") + .Length(256) + .Is.Required() .Display.Name("Rolle"); - Property(x => x.PasswordHash).Should.Not.ShowInDisplay().Should.Not.ShowInEditor(); - Property(x => x.ConfirmationKey).Should.Not.ShowInEditor().Should.Not.ShowInDisplay(); - Property(x => x.LastLogin).Should.Not.ShowInEditor() - .Display.Name("Letzte Anmeldung").Display.NullText(""); - Property(x => x.BounceCount).Should.Not.ShowInEditor() - .Display.Name("E-Mail Fehler"); - Property(x => x.Confirmed).Should.Not.HiddenInput() + Property(x => x.PasswordHash) + .Should.Not.ShowInDisplay() + .Should.Not.ShowInEditor(); + Property(x => x.ConfirmationKey) + .Should.Not.ShowInEditor() + .Should.Not.ShowInDisplay(); + Property(x => x.LastLogin) + .Should.Not.ShowInEditor() + .Display.Name("Letzte Anmeldung") + .Display.NullText("") + .Range(new DateTime(2010, 1, 23), DateTime.MaxValue); //support ends on doomsday + Property(x => x.BounceCount) + .Should.Not.ShowInEditor() + .Display.Name("E-Mail Fehler") + .AssertThat( + bc => ValidateBounceCountAgainstSomeConfiguration(bc), + "{0} is too high. Email address is considered invalid."); + Property(x => x.Confirmed) + .Should.Not.HiddenInput() .Display.Name("Bestätigt"); Property(x => x.Active) .Display.Name("Aktiv"); - Class.Display.Name("Benutzer"); + Class + .Display.Name("Benutzer") + .AssertThat( + u => u.Username != u.Autor.Name, + "{0}.Username and {0}.Autor.Name must not be equal"); + } + + bool ValidateBounceCountAgainstSomeConfiguration(int bounceCount) + { + return bounceCount < 3; } } @@ -30,7 +70,9 @@ public class AutorMetadata : DomainObjectMetadata { public AutorMetadata() { - Property(e => e.Name).Display.Name("emaN"); + Property(e => e.Name) + .Display.Name("emaN") + .Display.NullText("Anonymous Autor"); } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core.Specs/SampleClasses/WebUser.cs b/Source/FluentMetadata.Core.Specs/SampleClasses/WebUser.cs index f530437..424d1ae 100644 --- a/Source/FluentMetadata.Core.Specs/SampleClasses/WebUser.cs +++ b/Source/FluentMetadata.Core.Specs/SampleClasses/WebUser.cs @@ -9,11 +9,11 @@ public class Autor : DomainObject public class WebUser : DomainObject { - private WebUser() + internal WebUser() { } - public override void Initialize() + public override void Initialize() { base.Initialize(); PasswordHash = string.Empty; @@ -21,7 +21,7 @@ public override void Initialize() Active = false; } - public string Username { get; private set; } + public string Username { get; internal set; } public string EMail { get; private set; } @@ -33,7 +33,7 @@ public override void Initialize() public DateTime? LastLogin { get; private set; } public Guid? ConfirmationKey { get; private set; } public string Role { get; private set; } - public Autor Autor { get; private set; } + public Autor Autor { get; internal set; } public void SetEMailAddress(string emailAddress) { @@ -48,7 +48,7 @@ public void SetEMailAddress(string emailAddress) private string GetUserSalt() { - return (Created.Second*Created.DayOfYear).ToString(); + return (Created.Second * Created.DayOfYear).ToString(); } public void MailHasBounced() diff --git a/Source/FluentMetadata.Core/Builder/AsBuilder.cs b/Source/FluentMetadata.Core/Builder/AsBuilder.cs index 3c08180..0732289 100644 --- a/Source/FluentMetadata.Core/Builder/AsBuilder.cs +++ b/Source/FluentMetadata.Core/Builder/AsBuilder.cs @@ -11,7 +11,7 @@ public AsBuilder(PropertyMetadataBuilder propertyMetaDataBuilder) this.propertyMetaDataBuilder = propertyMetaDataBuilder; } - public IProperty EmailAddress() + public IProperty EmailAddress() { SetDataTypeName(DataType.EmailAddress); return propertyMetaDataBuilder; @@ -22,34 +22,40 @@ private void SetDataTypeName(DataType dataType) propertyMetaDataBuilder.Metadata.DataTypeName = dataType.ToString(); } - public IProperty Url() + public IProperty Url() { SetDataTypeName(DataType.Url); return propertyMetaDataBuilder; } - public IProperty Html() + public IProperty Html() { SetDataTypeName(DataType.Html); return propertyMetaDataBuilder; } - public IProperty Text() + public IProperty Text() { SetDataTypeName(DataType.Text); return propertyMetaDataBuilder; } - public IProperty MultilineText() + public IProperty MultilineText() { SetDataTypeName(DataType.MultilineText); return propertyMetaDataBuilder; } - public IProperty Password() + public IProperty Password() { SetDataTypeName(DataType.Password); return propertyMetaDataBuilder; } + + public IProperty Custom(string dataTypeName) + { + propertyMetaDataBuilder.Metadata.DataTypeName = dataTypeName; + return propertyMetaDataBuilder; + } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Builder/ClassMetadataBuilder.cs b/Source/FluentMetadata.Core/Builder/ClassMetadataBuilder.cs index f6bb82b..525726d 100644 --- a/Source/FluentMetadata.Core/Builder/ClassMetadataBuilder.cs +++ b/Source/FluentMetadata.Core/Builder/ClassMetadataBuilder.cs @@ -1,4 +1,6 @@ using System; +using System.Linq.Expressions; +using FluentMetadata.Rules; namespace FluentMetadata.Builder { @@ -11,18 +13,16 @@ internal class ClassMetadataBuilder : IClassBuilder public ClassMetadataBuilder(Metadata metadata) { this.metadata = metadata; - metadata.ModelType = typeof (T); -// metadata.ModelName = typeof (T).Name; + metadata.ModelType = typeof(T); InitPropertyMetadata(); } private void InitPropertyMetadata() { - string a; - var builder = FluentMetadataBuilder.GetTypeBuilder(typeof (T)); - foreach (var propertyInfo in typeof (T).GetProperties()) + var builder = FluentMetadataBuilder.GetTypeBuilder(typeof(T)); + foreach (var propertyInfo in typeof(T).GetProperties()) { - if (propertyInfo.GetIndexParameters().Length==0) + if (propertyInfo.GetIndexParameters().Length == 0) { var propertyMetadata = builder.MapProperty(typeof(T), propertyInfo.Name, propertyInfo.PropertyType); metadata.Properties.Add(propertyMetadata); @@ -44,5 +44,21 @@ public Metadata Metadata { get { return metadata; } } + + public IClassBuilder AssertThat(Func assertFunc, string errorMessageFormat) + { + metadata.AddRule(new GenericClassRule(errorMessageFormat, assertFunc)); + return this; + } + + public IPropertiesInClassContextBuilder Property(Expression> propertyExpression) + { + return new PropertiesInClassContextBuilder(this, propertyExpression); + } + + public IComparablePropertiesInClassContextBuilder ComparableProperty(Expression> propertyExpression) + { + return new ComparablePropertiesInClassContextBuilder(this, propertyExpression); + } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Builder/DisplayBuilder.cs b/Source/FluentMetadata.Core/Builder/DisplayBuilder.cs index df7d747..101e890 100644 --- a/Source/FluentMetadata.Core/Builder/DisplayBuilder.cs +++ b/Source/FluentMetadata.Core/Builder/DisplayBuilder.cs @@ -1,4 +1,6 @@ -namespace FluentMetadata.Builder +using System; + +namespace FluentMetadata.Builder { internal class DisplayBuilder : IDisplayClass { @@ -11,7 +13,13 @@ public DisplayBuilder(IClassBuilder classBuilder) public IClassBuilder Name(string displayName) { - classBuilder.Metadata.DisplayName = displayName; + classBuilder.Metadata.DisplayNameFunc = () => displayName; + return classBuilder; + } + + public IClassBuilder Name(Func displayNameFunc) + { + classBuilder.Metadata.DisplayNameFunc = displayNameFunc; return classBuilder; } @@ -22,28 +30,34 @@ public IClassBuilder Format(string displayFormat) } } - internal class DisplayBuilder : IDisplayProperty + internal class DisplayBuilder : IDisplayProperty { - private readonly PropertyMetadataBuilder propertyMetaDataBuilder; + private readonly PropertyMetadataBuilder propertyMetaDataBuilder; - public DisplayBuilder(PropertyMetadataBuilder propertyMetaDataBuilder) + public DisplayBuilder(PropertyMetadataBuilder propertyMetaDataBuilder) { this.propertyMetaDataBuilder = propertyMetaDataBuilder; } - public IProperty NullText(string nullDisplayText) + public IProperty NullText(string nullDisplayText) { propertyMetaDataBuilder.Metadata.NullDisplayText = nullDisplayText; return propertyMetaDataBuilder; } - public IProperty Name(string displayName) + public IProperty Name(string displayName) + { + propertyMetaDataBuilder.Metadata.DisplayNameFunc = () => displayName; + return propertyMetaDataBuilder; + } + + public IProperty Name(Func displayNameFunc) { - propertyMetaDataBuilder.Metadata.DisplayName = displayName; + propertyMetaDataBuilder.Metadata.DisplayNameFunc = displayNameFunc; return propertyMetaDataBuilder; } - public IProperty Format(string displayFormat) + public IProperty Format(string displayFormat) { propertyMetaDataBuilder.Metadata.DisplayFormat = displayFormat; return propertyMetaDataBuilder; diff --git a/Source/FluentMetadata.Core/Builder/ITypeMetadataBuilder.cs b/Source/FluentMetadata.Core/Builder/ITypeMetadataBuilder.cs deleted file mode 100644 index c698f3b..0000000 --- a/Source/FluentMetadata.Core/Builder/ITypeMetadataBuilder.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; -using System.Linq.Expressions; - -namespace FluentMetadata.Builder -{ -} \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Builder/IsBuilder.cs b/Source/FluentMetadata.Core/Builder/IsBuilder.cs index d6b5899..a87cad7 100644 --- a/Source/FluentMetadata.Core/Builder/IsBuilder.cs +++ b/Source/FluentMetadata.Core/Builder/IsBuilder.cs @@ -17,7 +17,7 @@ public IsBuilder(PropertyMetadataBuilder propertyMetaDataBuilder) this.propertyMetaDataBuilder = propertyMetaDataBuilder; } - public IProperty Required() + public IProperty Required() { Metadata.Required = !notted; notted = false; @@ -28,7 +28,7 @@ public IProperty Required() return propertyMetaDataBuilder; } - public IProperty ReadOnly() + public IProperty ReadOnly() { Metadata.Readonly = !notted; notted = false; @@ -44,5 +44,12 @@ public IIsNotProperty Not return this; } } + + public IProperty ConvertEmptyStringToNull() + { + Metadata.ConvertEmptyStringToNull = !notted; + notted = false; + return propertyMetaDataBuilder; + } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Builder/PropertiesInClassContextBuilder.cs b/Source/FluentMetadata.Core/Builder/PropertiesInClassContextBuilder.cs new file mode 100644 index 0000000..334bbdc --- /dev/null +++ b/Source/FluentMetadata.Core/Builder/PropertiesInClassContextBuilder.cs @@ -0,0 +1,42 @@ +using System; +using System.Linq.Expressions; +using FluentMetadata.Rules; + +namespace FluentMetadata.Builder +{ + internal class PropertiesInClassContextBuilder : IPropertiesInClassContextBuilder + { + readonly IClassBuilder classBuilder; + readonly Expression> propertyExpression; + + public PropertiesInClassContextBuilder(IClassBuilder classBuilder, Expression> propertyExpression) + { + this.classBuilder = classBuilder; + this.propertyExpression = propertyExpression; + } + + public IClassBuilder ShouldEqual(Expression> otherPropertyExpression) + { + classBuilder.Metadata.AddRule(new PropertyMustMatchRule(propertyExpression, otherPropertyExpression)); + return classBuilder; + } + } + + internal class ComparablePropertiesInClassContextBuilder : IComparablePropertiesInClassContextBuilder + { + readonly IClassBuilder classBuilder; + readonly Expression> propertyExpression; + + public ComparablePropertiesInClassContextBuilder(IClassBuilder classBuilder, Expression> propertyExpression) + { + this.classBuilder = classBuilder; + this.propertyExpression = propertyExpression; + } + + public IClassBuilder ShouldBeLessThan(Expression> otherPropertyExpression) + { + classBuilder.Metadata.AddRule(new PropertyMustBeLessThanOtherRule(propertyExpression, otherPropertyExpression)); + return classBuilder; + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Builder/PropertyMetadataBuilder.cs b/Source/FluentMetadata.Core/Builder/PropertyMetadataBuilder.cs index 5667206..1e99233 100644 --- a/Source/FluentMetadata.Core/Builder/PropertyMetadataBuilder.cs +++ b/Source/FluentMetadata.Core/Builder/PropertyMetadataBuilder.cs @@ -8,7 +8,8 @@ internal abstract class PropertyMetadataBuilder { private readonly Metadata metadata; - protected PropertyMetadataBuilder() : this(new Metadata()) + protected PropertyMetadataBuilder() + : this(new Metadata()) { } @@ -23,9 +24,8 @@ public Metadata Metadata } } - internal class PropertyMetadataBuilder : PropertyMetadataBuilder, IProperty + internal class PropertyMetadataBuilder : PropertyMetadataBuilder, IProperty { - public PropertyMetadataBuilder(Metadata metadata) : base(metadata) { @@ -33,45 +33,47 @@ public PropertyMetadataBuilder(Metadata metadata) public PropertyMetadataBuilder(Expression> expression) { - Metadata.ContainerType = typeof (T); + Metadata.ContainerType = typeof(T); Metadata.ModelName = ExpressionHelper.GetPropertyName(expression); Metadata.ModelType = ExpressionHelper.GetPropertyType(expression); } - - public PropertyMetadataBuilder(string propertyName) + public IProperty AssertThat(Func assertFunc, string errorMessageFormat) { - Metadata.ContainerType = null; - Metadata.ModelName = propertyName; - Metadata.ModelType = typeof (T); + Metadata.AddRule(new GenericRule(errorMessageFormat, assertFunc)); + return this; } + public IProperty Length(int maxLength) + { + Metadata.AddRule(new StringLengthRule(maxLength)); + return this; + } - public IProperty Length(int length) + public IProperty Length(int minLength, int? maxLength) { - Metadata.StringLength = length; - Metadata.AddRule(new StringLengthRule(length)); + Metadata.AddRule(new StringLengthRule(minLength, maxLength)); return this; } - public IProperty UIHint(string templateHint) + public IProperty UIHint(string templateHint) { Metadata.TemplateHint = templateHint; return this; } - public IProperty Description(string description) + public IProperty Description(string description) { Metadata.Description = description; return this; } - public IEditorProperty Editor + public IEditorProperty Editor { get { return new EditorBuilder(this); } } - public IDisplayProperty Display + public IDisplayProperty Display { get { return new DisplayBuilder(this); } } @@ -90,5 +92,11 @@ public IShouldProperty Should { get { return new ShouldBuilder(this); } } + + public IProperty Range(IComparable minimum, IComparable maximum) + { + Metadata.AddRule(new RangeRule(minimum, maximum)); + return this; + } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Builder/ShouldBuilder.cs b/Source/FluentMetadata.Core/Builder/ShouldBuilder.cs index c1d34fb..d1132e0 100644 --- a/Source/FluentMetadata.Core/Builder/ShouldBuilder.cs +++ b/Source/FluentMetadata.Core/Builder/ShouldBuilder.cs @@ -1,4 +1,6 @@ -namespace FluentMetadata.Builder +using FluentMetadata.Rules; + +namespace FluentMetadata.Builder { internal class ShouldBuilder : IShouldProperty { @@ -12,7 +14,7 @@ public ShouldBuilder(PropertyMetadataBuilder propertyMetaDataBuilder private Metadata Metadata { get { return propertyMetaDataBuilder.Metadata; } } - public IProperty HiddenInput() + public IProperty HiddenInput() { Metadata.Hidden = !notted; Metadata.HideSurroundingHtml = !notted; @@ -20,27 +22,36 @@ public IProperty HiddenInput() return propertyMetaDataBuilder; } - public IProperty ShowInDisplay() + public IProperty ShowInDisplay() { Metadata.ShowDisplay = !notted; notted = false; return propertyMetaDataBuilder; } - public IProperty ShowInEditor() + public IProperty ShowInEditor() { Metadata.ShowEditor = !notted; notted = false; return propertyMetaDataBuilder; } - public IProperty HideSurroundingHtml() + public IProperty HideSurroundingHtml() { Metadata.HideSurroundingHtml = !notted; notted = false; return propertyMetaDataBuilder; } + public IProperty MatchRegex(string pattern) + { + Metadata.AddRule(notted ? + new PropertyMustNotMatchRegexRule(pattern) : + new PropertyMustMatchRegexRule(pattern)); + notted = false; + return propertyMetaDataBuilder; + } + public IShouldNotProperty Not { get diff --git a/Source/FluentMetadata.Core/Builder/TypeMetaDataBuilder.cs b/Source/FluentMetadata.Core/Builder/TypeMetaDataBuilder.cs index fdeb2e6..92de2d5 100644 --- a/Source/FluentMetadata.Core/Builder/TypeMetaDataBuilder.cs +++ b/Source/FluentMetadata.Core/Builder/TypeMetaDataBuilder.cs @@ -36,8 +36,15 @@ protected bool TryGetPropertyBuilder(string propertyName, out PropertyMetadataBu public Metadata MapProperty(Type containerType, string propertyName, Type propertyType) { - var newMetaData = new Metadata() { ContainerType = containerType, ModelName = propertyName,ModelType = propertyType}; - return MapProperty(containerType, propertyName, newMetaData); + return MapProperty( + containerType, + propertyName, + new Metadata + { + ContainerType = containerType, + ModelName = propertyName, + ModelType = propertyType + }); } public abstract void Init(); @@ -50,11 +57,7 @@ public IProperty MapProperty(Expression> e return GetBuilder(expression); } - public TypeMetadataBuilder() - { - } - - private PropertyMetadataBuilder GetBuilder(Expression> expression) + PropertyMetadataBuilder GetBuilder(Expression> expression) { string propertyName = ExpressionHelper.GetPropertyName(expression); @@ -64,7 +67,7 @@ private PropertyMetadataBuilder GetBuilder(Expression(expression); PropertyBuilders.Add(propertyBuilder); } - return (PropertyMetadataBuilder) propertyBuilder; + return (PropertyMetadataBuilder)propertyBuilder; } public override Metadata MapProperty(Type containerType, string propertyName, Metadata metadata) @@ -85,28 +88,13 @@ public override void Init() ClassBuilder(); } - private PropertyMetadataBuilder CreatePropertyMetaDataBuilder(Metadata metadata, Type containerType, - Metadata newMetadata) - { - return (PropertyMetadataBuilder) typeof (PropertyMetadataBuilder<,>) - .CreateGenericInstance(containerType, metadata.ModelType, newMetadata); - } - - public IProperty MapEnum(object value) + PropertyMetadataBuilder CreatePropertyMetaDataBuilder(Metadata metadata, Type containerType, Metadata newMetadata) { - string propertyName = Enum.GetName(typeof (TResult), value); - PropertyMetadataBuilder builder; - if (!TryGetPropertyBuilder(propertyName, out builder)) - { - builder = new PropertyMetadataBuilder(propertyName); - PropertyBuilders.Add(builder); - } - return (IProperty) builder; + return (PropertyMetadataBuilder)typeof(PropertyMetadataBuilder<,>) + .CreateGenericInstance(containerType, metadata.ModelType, newMetadata); } - - private IClassBuilder classBuilder; - + IClassBuilder classBuilder; public IClassBuilder ClassBuilder() { if (classBuilder == null) diff --git a/Source/FluentMetadata.Core/ClassMetadata.cs b/Source/FluentMetadata.Core/ClassMetadata.cs index 5e57fb3..6f25944 100644 --- a/Source/FluentMetadata.Core/ClassMetadata.cs +++ b/Source/FluentMetadata.Core/ClassMetadata.cs @@ -1,7 +1,6 @@ using System; using System.Linq.Expressions; using FluentMetadata.Builder; -using FluentMetadata.Rules; namespace FluentMetadata { @@ -11,9 +10,12 @@ internal interface IClassMetadata public abstract class ClassMetadata : IClassMetadata { - protected ClassMetadata() + protected IClassBuilder Class { - GetTypeBuilder().ClassBuilder(); + get + { + return GetTypeBuilder().ClassBuilder(); + } } protected IProperty Property(Expression> expression) @@ -21,30 +23,14 @@ protected IProperty Property(Expression> e return GetTypeBuilder().MapProperty(expression); } - protected IProperty Property(T value) - { - return GetTypeBuilder().MapEnum(value); - } - - protected IClassBuilder Class - { - get { return GetTypeBuilder().ClassBuilder(); } - } - - protected void ClassRule(IClassRule classRule) - { - var typeBuilder = GetTypeBuilder(); - typeBuilder.Metadata.AddRule(classRule); - } - protected void CopyMetadataFrom() { - MetadataHelper.CopyMetadata(typeof (TBaseType), typeof (T)); + MetadataHelper.CopyMetadata(typeof(TBaseType), typeof(T)); } - private static TypeMetadataBuilder GetTypeBuilder() + static TypeMetadataBuilder GetTypeBuilder() { - return (TypeMetadataBuilder) FluentMetadataBuilder.GetTypeBuilder(); + return FluentMetadataBuilder.GetTypeBuilder(); } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/FluentMetadata.Core.csproj b/Source/FluentMetadata.Core/FluentMetadata.Core.csproj index 7b43fc4..1d8d8aa 100644 --- a/Source/FluentMetadata.Core/FluentMetadata.Core.csproj +++ b/Source/FluentMetadata.Core/FluentMetadata.Core.csproj @@ -70,13 +70,17 @@ - + + + + + @@ -95,6 +99,7 @@ + diff --git a/Source/FluentMetadata.Core/IAsProperty.cs b/Source/FluentMetadata.Core/IAsProperty.cs index 041217f..e46c815 100644 --- a/Source/FluentMetadata.Core/IAsProperty.cs +++ b/Source/FluentMetadata.Core/IAsProperty.cs @@ -2,11 +2,12 @@ { public interface IAsProperty { - IProperty EmailAddress(); - IProperty Url(); - IProperty Html(); - IProperty Text(); - IProperty MultilineText(); - IProperty Password(); + IProperty EmailAddress(); + IProperty Url(); + IProperty Html(); + IProperty Text(); + IProperty MultilineText(); + IProperty Password(); + IProperty Custom(string dataTypeName); } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/IClassBuilder.cs b/Source/FluentMetadata.Core/IClassBuilder.cs index 98f0cae..b88d8ba 100644 --- a/Source/FluentMetadata.Core/IClassBuilder.cs +++ b/Source/FluentMetadata.Core/IClassBuilder.cs @@ -1,8 +1,36 @@ -namespace FluentMetadata +using System; +using System.Linq.Expressions; + +namespace FluentMetadata { public interface IClassBuilder { Metadata Metadata { get; } IDisplayClass Display { get; } + + /// + /// Creates a generic class rule (i.e. a rule that is evaluated in class context) + /// asserting that the returns true. + /// + /// What to assert. + /// The error message format. Can contain a placeholder for {0} the the class display name. + /// + IClassBuilder AssertThat(Func assertFunc, string errorMessageFormat); + + /// + /// Entry point for class rules (i.e. rules that must be evaluated in class context) + /// that concern more than one property. + /// + /// The property expression. + /// + IPropertiesInClassContextBuilder Property(Expression> propertyExpression); + + /// + /// Entry point for class rules (i.e. rules that must be evaluated in class context) + /// that concern more than one property and need to compare properties. + /// + /// The property expression. + /// + IComparablePropertiesInClassContextBuilder ComparableProperty(Expression> propertyExpression); } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/IDisplayClass.cs b/Source/FluentMetadata.Core/IDisplayClass.cs index e8e96b7..d116774 100644 --- a/Source/FluentMetadata.Core/IDisplayClass.cs +++ b/Source/FluentMetadata.Core/IDisplayClass.cs @@ -1,8 +1,25 @@ -namespace FluentMetadata +using System; + +namespace FluentMetadata { public interface IDisplayClass { + /// + /// Sets the display name of the class. + /// Use this for static, i.e. culture invariant display names. + /// + /// The display name. + /// IClassBuilder Name(string displayName); + + /// + /// Sets the display name of the class. + /// Use this for dynamic, i.e. localized display names, e.g. resource strings. + /// + /// The display name. + /// + IClassBuilder Name(Func displayNameFunc); + IClassBuilder Format(string displayFormat); } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/IDisplayProperty.cs b/Source/FluentMetadata.Core/IDisplayProperty.cs index e605583..f098a99 100644 --- a/Source/FluentMetadata.Core/IDisplayProperty.cs +++ b/Source/FluentMetadata.Core/IDisplayProperty.cs @@ -1,9 +1,27 @@ -namespace FluentMetadata +using System; + +namespace FluentMetadata { - public interface IDisplayProperty + public interface IDisplayProperty { - IProperty NullText(string nullDisplayText); - IProperty Name(string displayName); - IProperty Format(string displayFormat); + IProperty NullText(string nullDisplayText); + + /// + /// Sets the display name of the property. + /// Use this for static, i.e. culture invariant display names. + /// + /// The display name. + /// + IProperty Name(string displayName); + + /// + /// Sets the display name of the property. + /// Use this for dynamic, i.e. localized display names, e.g. resource strings. + /// + /// The display name. + /// + IProperty Name(Func displayNameFunc); + + IProperty Format(string displayFormat); } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/IIsProperty.cs b/Source/FluentMetadata.Core/IIsProperty.cs index d7b9cdf..a698d97 100644 --- a/Source/FluentMetadata.Core/IIsProperty.cs +++ b/Source/FluentMetadata.Core/IIsProperty.cs @@ -1,16 +1,14 @@ -using System; -using System.Linq.Expressions; - -namespace FluentMetadata +namespace FluentMetadata { public interface IIsProperty : IIsNotProperty { IIsNotProperty Not { get; } } - public interface IIsNotProperty + public interface IIsNotProperty { - IProperty Required(); - IProperty ReadOnly(); + IProperty Required(); + IProperty ReadOnly(); + IProperty ConvertEmptyStringToNull(); } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/IPropertiesInClassContextBuilder.cs b/Source/FluentMetadata.Core/IPropertiesInClassContextBuilder.cs new file mode 100644 index 0000000..119c239 --- /dev/null +++ b/Source/FluentMetadata.Core/IPropertiesInClassContextBuilder.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq.Expressions; + +namespace FluentMetadata +{ + /// + /// Entry point for class rules (i.e. rules that must be evaluated in class context) + /// that concern more than one property. + /// + public interface IPropertiesInClassContextBuilder + { + /// + /// Adds a rule validating that the property's value is equal to the value of another property. + /// + /// The other property expression. + /// + IClassBuilder ShouldEqual(Expression> otherPropertyExpression); + } + + /// + /// Entry point for class rules (i.e. rules that must be evaluated in class context) + /// that concern more than one property and need to compare properties. + /// + public interface IComparablePropertiesInClassContextBuilder + { + /// + /// Adds a rule validating that the property's value is less than the value of another property. + /// + /// The other property expression. + /// + IClassBuilder ShouldBeLessThan(Expression> otherPropertyExpression); + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.Core/IProperty.cs b/Source/FluentMetadata.Core/IProperty.cs index 9d9c98f..73debd1 100644 --- a/Source/FluentMetadata.Core/IProperty.cs +++ b/Source/FluentMetadata.Core/IProperty.cs @@ -1,14 +1,17 @@ -namespace FluentMetadata +using System; + +namespace FluentMetadata { - public interface IProperty + public interface IProperty { - IProperty Length(int length); - IProperty UIHint(string templateHint); - - IProperty Description(string description); - - IEditorProperty Editor { get; } - IDisplayProperty Display { get; } + IProperty AssertThat(Func assertFunc, string errorMessageFormat); + IProperty Length(int maxLength); + IProperty Length(int minLength, int? maxLength); + IProperty UIHint(string templateHint); + IProperty Description(string description); + IProperty Range(IComparable minimum, IComparable maximum); + IEditorProperty Editor { get; } + IDisplayProperty Display { get; } IAsProperty As { get; } IIsProperty Is { get; } IShouldProperty Should { get; } diff --git a/Source/FluentMetadata.Core/IShouldProperty.cs b/Source/FluentMetadata.Core/IShouldProperty.cs index 6240baa..9d3f526 100644 --- a/Source/FluentMetadata.Core/IShouldProperty.cs +++ b/Source/FluentMetadata.Core/IShouldProperty.cs @@ -2,10 +2,11 @@ { public interface IShouldNotProperty { - IProperty HiddenInput(); - IProperty ShowInDisplay(); - IProperty ShowInEditor(); - IProperty HideSurroundingHtml(); + IProperty HiddenInput(); + IProperty ShowInDisplay(); + IProperty ShowInEditor(); + IProperty HideSurroundingHtml(); + IProperty MatchRegex(string pattern); } public interface IShouldProperty : IShouldNotProperty diff --git a/Source/FluentMetadata.Core/MetaData.cs b/Source/FluentMetadata.Core/MetaData.cs index 2d77d9c..c6e27d7 100644 --- a/Source/FluentMetadata.Core/MetaData.cs +++ b/Source/FluentMetadata.Core/MetaData.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentMetadata.Rules; namespace FluentMetadata @@ -11,6 +12,7 @@ public class Metadata public Metadata() { + ConvertEmptyStringToNull = true; ShowDisplay = true; ShowEditor = true; rules = new List(); @@ -28,21 +30,28 @@ public Metadata(Metadata metadata, Type containerType) internal void CopyMetaDataFrom(Metadata metadata) { - Required = metadata.Required; - StringLength = metadata.StringLength; - ErrorMessage = metadata.ErrorMessage; + //TODO write tests for CopyMetaDataFrom: properties commented here have not associated tests yet + ConvertEmptyStringToNull = metadata.ConvertEmptyStringToNull; DataTypeName = metadata.DataTypeName; + Description = metadata.Description; + DisplayFormat = metadata.DisplayFormat; + DisplayNameFunc = metadata.DisplayNameFunc; + EditorFormat = metadata.EditorFormat; + HideSurroundingHtml = metadata.HideSurroundingHtml; Readonly = metadata.Readonly; + Required = metadata.Required; + NullDisplayText = metadata.NullDisplayText; ShowDisplay = metadata.ShowDisplay; ShowEditor = metadata.ShowEditor; TemplateHint = metadata.TemplateHint; - NullDisplayText = metadata.NullDisplayText; - DisplayName = metadata.DisplayName; + Watermark = metadata.Watermark; + //ErrorMessage = metadata.ErrorMessage; + Hidden = metadata.Hidden; + foreach (var rule in metadata.Rules) { AddRule(rule); } - } #region properties corresponding to System.Web.Mvc.ModelMetadata @@ -52,7 +61,7 @@ internal void CopyMetaDataFrom(Metadata metadata) // // Returns: // A dictionary that contains additional metadata about the model. - // TODO MVC2 public virtual Dictionary AdditionalValues { get; } + // TODO MVC2 [in order to complete properties corresponding to System.Web.Mvc.ModelMetadata] public virtual Dictionary AdditionalValues { get; } // ~ System.Web.Mvc.ModelMetadata.ContainerType /// @@ -63,15 +72,15 @@ internal void CopyMetaDataFrom(Metadata metadata) /// public Type ContainerType { get; set; } - // - // Summary: - // Gets or sets a value that indicates whether empty strings that are posted - // back in forms should be converted to null. - // - // Returns: - // true if empty strings that are posted back in forms should be converted to - // null; otherwise, false. The default value is true. - // TODO MVC2 public virtual bool ConvertEmptyStringToNull { get; set; } + /// + /// Gets or sets a value that indicates whether empty strings that are posted + /// back in forms should be converted to null. + /// + /// + /// true if empty strings that are posted back in forms should be + /// converted to null; otherwise, false. The default value is true + /// + public bool ConvertEmptyStringToNull { get; set; } // ~ System.Web.Mvc.ModelMetadata.DataTypeName /// @@ -102,12 +111,12 @@ internal void CopyMetaDataFrom(Metadata metadata) // ~ System.Web.Mvc.ModelMetadata.DisplayName /// - /// Gets or sets the display name of the model. + /// Gets or sets the GetDisplayName function of the model. /// /// - /// The display name of the model. + /// The GetDisplayName function of the model. /// - public string DisplayName { get; set; } + internal Func DisplayNameFunc { private get; set; } // ~ System.Web.Mvc.ModelMetadata.EditFormatString /// @@ -136,7 +145,7 @@ internal void CopyMetaDataFrom(Metadata metadata) // Returns: // A value that indicates whether the model is considered a complex type by // the MVC framework. - // TODO MVC2 ? public virtual bool IsComplexType { get; } + // MVC2 [in order to complete properties corresponding to System.Web.Mvc.ModelMetadata] public virtual bool IsComplexType { get; } // // Summary: @@ -144,7 +153,7 @@ internal void CopyMetaDataFrom(Metadata metadata) // // Returns: // true if the type is nullable; otherwise, false. - // TODO MVC2 ? public bool IsNullableValueType { get; } + // MVC2 [in order to complete properties corresponding to System.Web.Mvc.ModelMetadata] public bool IsNullableValueType { get; } // ~ System.Web.Mvc.ModelMetadata.IsReadOnly /// @@ -172,7 +181,7 @@ internal void CopyMetaDataFrom(Metadata metadata) // The value of the model. For more information about System.Web.Mvc.ModelMetadata, // see the entry ASP.NET MVC 2 Templates, Part 2: ModelMetadata on Brad Wilson's // blog - // TODO MVC2 ? public object Model { get; set; } + // TODO MVC2 [in order to complete properties corresponding to System.Web.Mvc.ModelMetadata] public object Model { get; set; } // ~ System.Web.Mvc.ModelMetadata.ModelType /// @@ -198,7 +207,7 @@ internal void CopyMetaDataFrom(Metadata metadata) // // Returns: // The order value of the current metadata. - // TODO MVC3 public virtual int Order { get; set; } + // TODO MVC3 [in order to complete properties corresponding to System.Web.Mvc.ModelMetadata] public virtual int Order { get; set; } // ~ System.Web.Mvc.ModelMetadata.Properties /// @@ -229,7 +238,7 @@ internal void CopyMetaDataFrom(Metadata metadata) // // Returns: // The short display name. - // TODO MVC2 public virtual string ShortDisplayName { get; set; } + // TODO MVC2 [in order to complete properties corresponding to System.Web.Mvc.ModelMetadata] public virtual string ShortDisplayName { get; set; } // ~ System.Web.Mvc.ModelMetadata.ShowForDisplay /// @@ -255,10 +264,10 @@ internal void CopyMetaDataFrom(Metadata metadata) // // Returns: // The simple display string for the model. - // TODO MVC2 public virtual string SimpleDisplayText { get; set; } + // TODO MVC2 [in order to complete properties corresponding to System.Web.Mvc.ModelMetadata] public virtual string SimpleDisplayText { get; set; } /// - /// Gets or sets the template hint. + /// Gets or sets a hint that suggests what template to use for this model. /// /// /// A hint that suggests what template to use for this model. @@ -266,7 +275,7 @@ internal void CopyMetaDataFrom(Metadata metadata) public string TemplateHint { get; set; } /// - /// Gets or sets a hint that suggests what template to use for this model. + /// Gets or sets a value that can be used as a watermark. /// /// /// The watermark. @@ -275,9 +284,16 @@ internal void CopyMetaDataFrom(Metadata metadata) #endregion - // TODO add StringLengthRule to rules automatically on set - public int? StringLength { get; set; } + //TODO [DerAlbertCom] What kind of ErrorMessage is this? Write some XML docs and/or tests. public string ErrorMessage { get; set; } + + //~System.Web.Mvc.HiddenInputAttribute + /// + /// Gets or sets a value indicating whether a property or field value should be rendered as a hidden input element. + /// + /// + /// true if the model should be rendered as a hidden input element; otherwise, false. + /// public bool? Hidden { get; set; } public IEnumerable Rules @@ -289,5 +305,49 @@ public void AddRule(IRule rule) { rules.Add(rule); } + + public string GetDisplayName() + { + return DisplayNameFunc == null ? + null : + DisplayNameFunc(); + } + + public object GetRangeMinimum() + { + var rangeRule = GetLastRuleOfType(); + return rangeRule == null ? null : rangeRule.Minimum; + } + + public object GetRangeMaximum() + { + var rangeRule = GetLastRuleOfType(); + return rangeRule == null ? null : rangeRule.Maximum; + } + + public int? GetMaximumLength() + { + var lengthRule = GetLastRuleOfType(); + if (lengthRule == null) + { + return null; + } + return lengthRule.Maximum; + } + + public int? GetMinimumLength() + { + var lengthRule = GetLastRuleOfType(); + if (lengthRule == null) + { + return null; + } + return lengthRule.Minimum; + } + + T GetLastRuleOfType() + { + return Rules.OfType().LastOrDefault(); + } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/MetadataHelper.cs b/Source/FluentMetadata.Core/MetadataHelper.cs index 8e5e185..df15e1c 100644 --- a/Source/FluentMetadata.Core/MetadataHelper.cs +++ b/Source/FluentMetadata.Core/MetadataHelper.cs @@ -1,16 +1,19 @@ using System; +using System.Collections.Generic; +using System.Linq; using FluentMetadata.Builder; namespace FluentMetadata { public static class MetadataHelper { - private static QueryFluentMetadata query = new QueryFluentMetadata(); + static QueryFluentMetadata query = new QueryFluentMetadata(); + public static void CopyMetadata(Type from, Type to) { var toBuilder = FluentMetadataBuilder.GetTypeBuilder(to); var nameBuilder = new PropertyNameMetadataBuilder(from); - + //copy property metadata foreach (var namedMetaData in nameBuilder.NamedMetaData) { var propertyInfo = to.GetProperty(namedMetaData.PropertyName); @@ -19,12 +22,36 @@ public static void CopyMetadata(Type from, Type to) toBuilder.MapProperty(to, namedMetaData.PropertyName, namedMetaData.Metadata); } } + //copy type metadata query.GetMetadataFor(to).CopyMetaDataFrom(query.GetMetadataFor(from)); } public static void CopyMetadataFrom() { - CopyMetadata(typeof (T), typeof (TBaseType)); + CopyMetadata(typeof(T), typeof(TBaseType)); + } + + public static void CopyMappedMetadata(Type from, Type to, IEnumerable memberMaps) + { + var toBuilder = FluentMetadataBuilder.GetTypeBuilder(to); + var fromBuilder = new PropertyNameMetadataBuilder(from); + //copy property metadata + foreach (var fromMetaData in fromBuilder.NamedMetaData) + { + var memberMap = memberMaps.SingleOrDefault(mm => mm.SourceName == fromMetaData.PropertyName); + if (memberMap != null) + { + toBuilder.MapProperty(to, memberMap.DestinationName, fromMetaData.Metadata); + } + } + //copy type metadata + query.GetMetadataFor(to).CopyMetaDataFrom(query.GetMetadataFor(from)); } } + + public class MemberMap + { + public string SourceName { get; set; } + public string DestinationName { get; set; } + } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/QueryFluentMetadata.cs b/Source/FluentMetadata.Core/QueryFluentMetadata.cs index 6131f1b..88681b8 100644 --- a/Source/FluentMetadata.Core/QueryFluentMetadata.cs +++ b/Source/FluentMetadata.Core/QueryFluentMetadata.cs @@ -6,28 +6,25 @@ public class QueryFluentMetadata { public Metadata GetMetadataFor(Type type) { - var builder = FluentMetadataBuilder.GetTypeBuilder(type); - return builder.Metadata; + return FluentMetadataBuilder.GetTypeBuilder(type).Metadata; } public Metadata GetMetadataFor(Type type, string propertyName) { - var metadata = GetMetadataFor(type); - if (!metadata.Properties.Contains(propertyName)) + var metadataProperties = GetMetadataFor(type).Properties; + if (!metadataProperties.Contains(propertyName)) { - throw new ArgumentOutOfRangeException("propertyName","Unknow Property"); + throw new ArgumentOutOfRangeException("propertyName", "Unknown Property"); } - return metadata.Properties[propertyName]; + return metadataProperties[propertyName]; } public Metadata FindMetadataFor(Type type, string propertyName) { - var metadata = GetMetadataFor(type); - if (!metadata.Properties.Contains(propertyName)) - { - return null; - } - return metadata.Properties[propertyName]; + var metadataProperties = GetMetadataFor(type).Properties; + return metadataProperties.Contains(propertyName) ? + metadataProperties[propertyName] : + null; } } } \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Rules/ClassRule.cs b/Source/FluentMetadata.Core/Rules/ClassRule.cs index 8936949..44037b5 100644 --- a/Source/FluentMetadata.Core/Rules/ClassRule.cs +++ b/Source/FluentMetadata.Core/Rules/ClassRule.cs @@ -1,6 +1,4 @@ -using System; - -namespace FluentMetadata.Rules +namespace FluentMetadata.Rules { public abstract class ClassRule : IClassRule { @@ -15,7 +13,7 @@ protected ClassRule(string errorMessageFormat) public bool IsValid(object value) { - return IsValid((T) value); + return IsValid((T)value); } public abstract string FormatErrorMessage(string name); diff --git a/Source/FluentMetadata.Core/Rules/GenericClassRule.cs b/Source/FluentMetadata.Core/Rules/GenericClassRule.cs new file mode 100644 index 0000000..8b646cf --- /dev/null +++ b/Source/FluentMetadata.Core/Rules/GenericClassRule.cs @@ -0,0 +1,25 @@ +using System; + +namespace FluentMetadata.Rules +{ + class GenericClassRule : ClassRule + { + readonly Func assertFunc; + + public GenericClassRule(string errorMessageFormat, Func assertFunc) + : base(errorMessageFormat) + { + this.assertFunc = assertFunc; + } + + public override bool IsValid(T instance) + { + return assertFunc(instance); + } + + public override string FormatErrorMessage(string name) + { + return string.Format(ErrorMessageFormat, name); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Rules/GenericRule.cs b/Source/FluentMetadata.Core/Rules/GenericRule.cs new file mode 100644 index 0000000..c087312 --- /dev/null +++ b/Source/FluentMetadata.Core/Rules/GenericRule.cs @@ -0,0 +1,30 @@ +using System; + +namespace FluentMetadata.Rules +{ + class GenericRule : Rule + { + readonly Func assertFunc; + + public GenericRule(string errorMessageFormat, Func assertFunc) + : base(errorMessageFormat) + { + this.assertFunc = assertFunc; + } + + public override string FormatErrorMessage(string name) + { + return string.Format(ErrorMessageFormat, name); + } + + public override bool IsValid(object value) + { + return IsValid((TProperty)value); + } + + public bool IsValid(TProperty value) + { + return assertFunc(value); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Rules/PropertyMustBeLessThanOtherRule.cs b/Source/FluentMetadata.Core/Rules/PropertyMustBeLessThanOtherRule.cs new file mode 100644 index 0000000..32098ea --- /dev/null +++ b/Source/FluentMetadata.Core/Rules/PropertyMustBeLessThanOtherRule.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq.Expressions; + +namespace FluentMetadata.Rules +{ + public class PropertyMustBeLessThanOtherRule : ClassRule + { + readonly string proptertyName, otherPropertyName; + readonly Func propertyFunc, otherPropertyFunc; + + public PropertyMustBeLessThanOtherRule( + Expression> propertyExpression, + Expression> otherPropertyExpression) + : base("The value of '{0}.{1}' must be less than the value of '{0}.{2}'.") + { + proptertyName = ((propertyExpression.Body as UnaryExpression).Operand as MemberExpression).Member.Name; + propertyFunc = propertyExpression.Compile(); + otherPropertyName = ((otherPropertyExpression.Body as UnaryExpression).Operand as MemberExpression).Member.Name; + otherPropertyFunc = otherPropertyExpression.Compile(); + } + + public override bool IsValid(T instance) + { + return propertyFunc(instance) + .CompareTo(otherPropertyFunc(instance)) < 0; + } + + public override string FormatErrorMessage(string name) + { + return string.Format( + this.ErrorMessageFormat, + name, + proptertyName, + otherPropertyName); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Rules/PropertyMustMatchRegexRule.cs b/Source/FluentMetadata.Core/Rules/PropertyMustMatchRegexRule.cs new file mode 100644 index 0000000..566c1e1 --- /dev/null +++ b/Source/FluentMetadata.Core/Rules/PropertyMustMatchRegexRule.cs @@ -0,0 +1,57 @@ +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace FluentMetadata.Rules +{ + public class PropertyMustMatchRegexRule : Rule + { + Regex regex; + + public PropertyMustMatchRegexRule(string pattern) + : base("the value for {0} is not in the correct format") + { + regex = new Regex(pattern); + } + + public override bool IsValid(object value) + { + var valueAsString = Convert.ToString(value, CultureInfo.CurrentCulture); + // because validating this is not the responsibility of the PropertyMustMatchRegexRule + if (string.IsNullOrEmpty(valueAsString)) + { + return true; + } + return Matches(valueAsString); + } + + protected bool Matches(string value) + { + return regex.Match(value).Success; + } + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageFormat, name); + } + } + + public class PropertyMustNotMatchRegexRule : PropertyMustMatchRegexRule + { + public PropertyMustNotMatchRegexRule(string pattern) + : base(pattern) + { + } + + public override bool IsValid(object value) + { + var valueAsString = Convert.ToString(value, CultureInfo.CurrentCulture); + // because validating this is not the responsibility of the PropertyMustNotMatchRegexRule + if (string.IsNullOrEmpty(valueAsString)) + { + return true; + } + return !Matches(valueAsString); + } + } +} \ No newline at end of file diff --git a/Source/FluentMetadata.Core/Rules/PropertyMustMatchRule.cs b/Source/FluentMetadata.Core/Rules/PropertyMustMatchRule.cs index c42ab1b..1e2321b 100644 --- a/Source/FluentMetadata.Core/Rules/PropertyMustMatchRule.cs +++ b/Source/FluentMetadata.Core/Rules/PropertyMustMatchRule.cs @@ -4,17 +4,18 @@ namespace FluentMetadata.Rules { - public class PropertyMustMatchRule : ClassRule where T : class + public class PropertyMustMatchRule : ClassRule { private const string DefaultErrorMessage = "'{0}' and '{1}' do not match."; - private readonly string originalPropertyName; private readonly string confirmPropertyName; private Type currentType; - public PropertyMustMatchRule(Expression> expression, - Expression> confirmExpression) : base(DefaultErrorMessage) + public PropertyMustMatchRule( + Expression> expression, + Expression> confirmExpression) + : base(DefaultErrorMessage) { originalPropertyName = ExpressionHelper.GetPropertyName(expression); confirmPropertyName = ExpressionHelper.GetPropertyName(confirmExpression); @@ -22,11 +23,12 @@ public PropertyMustMatchRule(Expression> expression, public override string FormatErrorMessage(string name) { - return String.Format(CultureInfo.CurrentCulture, - ErrorMessageFormat, - GetPropertyDisplayName(originalPropertyName), - GetPropertyDisplayName(confirmPropertyName) - ); + return String.Format( + CultureInfo.CurrentCulture, + ErrorMessageFormat, + GetPropertyDisplayName(originalPropertyName), + GetPropertyDisplayName(confirmPropertyName) + ); } private string GetPropertyDisplayName(string propertyName) @@ -34,7 +36,10 @@ private string GetPropertyDisplayName(string propertyName) var metaData = FluentMetadataBuilder.GetTypeBuilder(currentType).MetaDataFor(propertyName); if (metaData != null) { - propertyName = string.IsNullOrEmpty(metaData.DisplayName) ? propertyName : metaData.DisplayName; + var metaDataDisplayName = metaData.GetDisplayName(); + propertyName = string.IsNullOrEmpty(metaDataDisplayName) ? + propertyName : + metaDataDisplayName; } return propertyName; } @@ -45,8 +50,10 @@ public override bool IsValid(T instance) return true; currentType = instance.GetType(); - return Equals(GetValueFromProperty(instance, originalPropertyName), - GetValueFromProperty(instance, confirmPropertyName)); + return Equals( + GetValueFromProperty(instance, originalPropertyName), + GetValueFromProperty(instance, confirmPropertyName) + ); } private static object GetValueFromProperty(object instance, string propertyName) diff --git a/Source/FluentMetadata.Core/Rules/RangeRule.cs b/Source/FluentMetadata.Core/Rules/RangeRule.cs index ebca20a..62ab350 100644 --- a/Source/FluentMetadata.Core/Rules/RangeRule.cs +++ b/Source/FluentMetadata.Core/Rules/RangeRule.cs @@ -5,26 +5,31 @@ namespace FluentMetadata.Rules { public class RangeRule : Rule { - private Func valueConversion; - private object valueMaximum; - private object valueMinimum; + private IComparable valueMaximum; + private IComparable valueMinimum; - private RangeRule() - : base("the value of {0} must be between {0} and {1}") + internal object Minimum { + get + { + return valueMinimum; + } } - public RangeRule(double minimum, double maximum) : this() + internal object Maximum { - Initialize(minimum, maximum, o => Convert.ToDouble(o)); + get + { + return valueMaximum; + } } - public RangeRule(int minimum, int maximum) : this() + private RangeRule() + : base("the value of '{0}' must be between {1} and {2}") { - Initialize(minimum, maximum, o => Convert.ToInt32(o)); } - public RangeRule(DateTime minimum, DateTime maximum) + public RangeRule(IComparable minimum, IComparable maximum) : this() { Initialize(minimum, maximum, o => Convert.ToDateTime(o)); @@ -34,14 +39,19 @@ private void Initialize(IComparable minimum, IComparable maximum, Func 0) { - throw new ArgumentOutOfRangeException("maximum", maximum, - string.Format(CultureInfo.CurrentCulture, - "the minimum vallue {1} is higher then the maximum value {0}", - minimum, maximum)); + throw new ArgumentOutOfRangeException( + "maximum", + maximum, + string.Format( + CultureInfo.CurrentCulture, + "the minimum value {0} is higher then the maximum value {1}", + minimum, + maximum + ) + ); } valueMinimum = minimum; valueMaximum = maximum; - valueConversion = conversion; } public override bool IsValid(object value) @@ -50,14 +60,13 @@ public override bool IsValid(object value) { return true; } - if ((value is string) && string.IsNullOrEmpty((string) value)) + if ((value is string) && string.IsNullOrEmpty(value as string)) { return true; } - object currentValue = valueConversion(value); - var min = (IComparable) valueMinimum; - var max = (IComparable) valueMaximum; - return ((min.CompareTo(currentValue) <= 0) && (max.CompareTo(currentValue) >= 0)); + var min = (IComparable)valueMinimum; + var max = (IComparable)valueMaximum; + return ((min.CompareTo(value) <= 0) && (max.CompareTo(value) >= 0)); } public override string FormatErrorMessage(string name) diff --git a/Source/FluentMetadata.Core/Rules/StringLengthRule.cs b/Source/FluentMetadata.Core/Rules/StringLengthRule.cs index 71d0c58..05f0033 100644 --- a/Source/FluentMetadata.Core/Rules/StringLengthRule.cs +++ b/Source/FluentMetadata.Core/Rules/StringLengthRule.cs @@ -3,36 +3,69 @@ namespace FluentMetadata.Rules { // ~ System.ComponentModel.DataAnnotations.StringLengthAttribute.MaximumLength + // ~ System.ComponentModel.DataAnnotations.StringLengthAttribute.MinimumLength public class StringLengthRule : Rule { - private readonly int maxLength; + readonly int? minLength, maxLength; + + internal int? Minimum + { + get + { + return minLength; + } + } + + internal int? Maximum + { + get + { + return maxLength; + } + } public StringLengthRule(int maxLength) - : base("the string for {0} should be longer than {1} characters") + : base("the string for '{0}' should not be longer than {1} characters") + { + this.maxLength = maxLength; + } + + public StringLengthRule(int minLength, int? maxLength) + : base("'{0}' must be " + + (maxLength.HasValue ? " between {2} and {1}" : " at least {2}") + + " characters long") { + this.minLength = minLength; this.maxLength = maxLength; } public override bool IsValid(object value) { - if (value == null) + var valueAsString = value as string; + if (valueAsString == null) { - return true; + return minLength.HasValue ? false : true; } - var strValue = (string)value; - return strValue.Length <= maxLength; + + var length = valueAsString.Length; + if (maxLength.HasValue && length > maxLength || + minLength.HasValue && length < minLength) + { + return false; + } + + return true; } public override string FormatErrorMessage(string name) { - return string.Format(ErrorMessageFormat, name, maxLength); + return string.Format(ErrorMessageFormat, name, maxLength, minLength); } } - // TODO rule equivalent to System.ComponentModel.DataAnnotations.StringLengthAttribute.MinimumLength // TODO rule equivalent to System.ComponentModel.DataAnnotations.RegularExpressionAttribute - // TODO implement or delete: What does this rule validate? + //TODO [DerAlbertCom] implement or delete: What does this rule validate? public class EqualToRule : Rule { public EqualToRule(string errorMessageFormat) diff --git a/Source/FluentMetadata.EntityFramework.Specs/DbContextTest.cs b/Source/FluentMetadata.EntityFramework.Specs/DbContextTest.cs index ea6abff..f60b0bd 100644 --- a/Source/FluentMetadata.EntityFramework.Specs/DbContextTest.cs +++ b/Source/FluentMetadata.EntityFramework.Specs/DbContextTest.cs @@ -1,7 +1,5 @@ -using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; -using System.Data.Entity.ModelConfiguration; using System.IO; using Xunit; @@ -14,7 +12,8 @@ public DbContextTest() Database.SetInitializer(new AlwaysRecreateDatabase()); } - [Fact] + //TODO refactor this test. It breaks the build if Entity Framework is not installed + [Fact(Skip = "This test requires Entity Framework to be installed")] public void CanCreateDbContext() { if (File.Exists("TestDatabase.sdf")) @@ -33,7 +32,6 @@ public class NoDatabaseCreate : IDatabaseInitializer where T : DbContext { public void InitializeDatabase(T context) { - } } } \ No newline at end of file diff --git a/Source/FluentMetadata.EntityFramework.Specs/FluentMetadata.EntityFramework.Specs.csproj b/Source/FluentMetadata.EntityFramework.Specs/FluentMetadata.EntityFramework.Specs.csproj index 19c0bdd..6c4e930 100644 --- a/Source/FluentMetadata.EntityFramework.Specs/FluentMetadata.EntityFramework.Specs.csproj +++ b/Source/FluentMetadata.EntityFramework.Specs/FluentMetadata.EntityFramework.Specs.csproj @@ -13,6 +13,21 @@ FluentMetadata.EntityFramework.Specs v4.0 512 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true true @@ -22,6 +37,7 @@ DEBUG;TRACE prompt 4 + AllRules.ruleset pdbonly @@ -30,6 +46,7 @@ TRACE prompt 4 + AllRules.ruleset @@ -105,6 +122,28 @@ FluentMetadata.EntityFramework + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + \ No newline at end of file diff --git a/Source/FluentMetadata.FluentNHibernate.Specs/Properties/AssemblyInfo.cs b/Source/FluentMetadata.FluentNHibernate.Specs/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9e3ce2b --- /dev/null +++ b/Source/FluentMetadata.FluentNHibernate.Specs/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FluentMetadata.FluentNHibernate.Specs")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FluentMetadata.FluentNHibernate.Specs")] +[assembly: AssemblyCopyright("Copyright © 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bc0543ee-1db5-4a27-bb0b-073ebbc5a070")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/FluentMetadata.FluentNHibernate.Specs/packages.config b/Source/FluentMetadata.FluentNHibernate.Specs/packages.config new file mode 100644 index 0000000..ba66ae1 --- /dev/null +++ b/Source/FluentMetadata.FluentNHibernate.Specs/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Source/FluentMetadata.FluentNHibernate/Conventions/FluentMetaDataConvention.cs b/Source/FluentMetadata.FluentNHibernate/Conventions/FluentMetaDataConvention.cs index 8d496e8..fbd0dcb 100644 --- a/Source/FluentMetadata.FluentNHibernate/Conventions/FluentMetaDataConvention.cs +++ b/Source/FluentMetadata.FluentNHibernate/Conventions/FluentMetaDataConvention.cs @@ -3,35 +3,34 @@ namespace FluentMetadata.FluentNHibernate.Conventions { - public class FluentMetaDataConvention : IPropertyConvention + public class FluentMetaDataConvention : IPropertyConvention, IReferenceConvention { - private readonly QueryFluentMetadata query = new QueryFluentMetadata(); + readonly QueryFluentMetadata query = new QueryFluentMetadata(); + public void Apply(IPropertyInstance instance) { - var meta = query.GetMetadataFor(instance.EntityType,instance.Property.Name); - if (meta.Required.HasValue) - { - ApplyRequired(meta.Required.Value, instance); - } - if (meta.StringLength.HasValue) + var meta = query.FindMetadataFor(instance.EntityType, instance.Property.Name); + if (meta != null) { - ApplyStringLength(meta.StringLength.Value, instance); - } - } + if (meta.Required.HasValue && meta.Required.Value) + { + instance.Not.Nullable(); + } - private static void ApplyStringLength(int stringLength, IPropertyInstance target) - { - if (stringLength > 0) - { - target.Length(stringLength); + var maxLength = meta.GetMaximumLength(); + if (maxLength.HasValue && maxLength.Value > 0) + { + instance.Length(maxLength.Value); + } } } - private static void ApplyRequired(bool required, IPropertyInstance target) + public void Apply(IManyToOneInstance instance) { - if (required) + var meta = query.FindMetadataFor(instance.EntityType, instance.Property.Name); + if (meta != null && meta.Required.HasValue && meta.Required.Value) { - target.Not.Nullable(); + instance.Not.Nullable(); } } } diff --git a/Source/FluentMetadata.FluentNHibernate/FluentMetadata.FluentNHibernate.csproj b/Source/FluentMetadata.FluentNHibernate/FluentMetadata.FluentNHibernate.csproj index 36d7301..74486ab 100644 --- a/Source/FluentMetadata.FluentNHibernate/FluentMetadata.FluentNHibernate.csproj +++ b/Source/FluentMetadata.FluentNHibernate/FluentMetadata.FluentNHibernate.csproj @@ -53,9 +53,8 @@ AllRules.ruleset - - False - ..\..\external\NHibernate\Additional\FluentNHibernate.dll + + ..\packages\FluentNHibernate.1.3.0.717\lib\FluentNHibernate.dll @@ -100,6 +99,9 @@ true + + +