{8f0618d8-8200-45e9-9d1e-b268e98e0a84}
diff --git a/Tests/DbLocalizationProvider.MvcSample/Models/HomeViewModel.cs b/Tests/DbLocalizationProvider.MvcSample/Models/HomeViewModel.cs
index 54d5418c..2f060093 100644
--- a/Tests/DbLocalizationProvider.MvcSample/Models/HomeViewModel.cs
+++ b/Tests/DbLocalizationProvider.MvcSample/Models/HomeViewModel.cs
@@ -10,14 +10,20 @@ public class BaseViewModel
[StringLength(100, MinimumLength = 5)]
public string Message { get; set; }
+ [Display(Name = "Base username:", Description = "")]
+ [StringLength(100, MinimumLength = 5)]
+ [UIHint("Username")]
+ public string BaseUsername { get; set; }
+
public string CustomMessage { get; } = "Resource like property on base view model";
}
[LocalizedModel(Inherited = false)]
public class HomeViewModel : BaseViewModel
{
- [Display(Name = "The user name:")]
+ [Display(Name = "The user name:", Description = "")]
[Required]
+ [UIHint("Username")]
public string Username { get; set; }
}
}
diff --git a/Tests/DbLocalizationProvider.MvcSample/Views/Home/Index.cshtml b/Tests/DbLocalizationProvider.MvcSample/Views/Home/Index.cshtml
index 237c8fa8..e55eba9c 100644
--- a/Tests/DbLocalizationProvider.MvcSample/Views/Home/Index.cshtml
+++ b/Tests/DbLocalizationProvider.MvcSample/Views/Home/Index.cshtml
@@ -23,9 +23,10 @@
@Html.ValidationMessageFor(m => m.Message)
- @Html.LabelFor(m => m.Username)
@Html.EditorFor(m => m.Username)
- @Html.ValidationMessageFor(m => m.Username)
+
+
+ @Html.EditorFor(m => m.BaseUsername)
diff --git a/Tests/DbLocalizationProvider.MvcSample/Views/Shared/EditorTemplates/Username.cshtml b/Tests/DbLocalizationProvider.MvcSample/Views/Shared/EditorTemplates/Username.cshtml
new file mode 100644
index 00000000..8b68958b
--- /dev/null
+++ b/Tests/DbLocalizationProvider.MvcSample/Views/Shared/EditorTemplates/Username.cshtml
@@ -0,0 +1,7 @@
+@model string
+
+@Html.LabelFor(m => m)
+@Html.TextBoxFor(m => m)
+@Html.ValidationMessageFor(m => m)
+
+[@ViewData.ModelMetadata.Description]
\ No newline at end of file
diff --git a/Tests/DbLocalizationProvider.Tests/DataAnnotations/BaseViewModel.cs b/Tests/DbLocalizationProvider.Tests/DataAnnotations/BaseViewModel.cs
new file mode 100644
index 00000000..32761750
--- /dev/null
+++ b/Tests/DbLocalizationProvider.Tests/DataAnnotations/BaseViewModel.cs
@@ -0,0 +1,12 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace DbLocalizationProvider.Tests.DataAnnotations
+{
+ [LocalizedModel]
+ public class BaseViewModel
+ {
+ [Display(Name = "Base property", Description = "")]
+ [Required]
+ public string BaseProperty { get; set; }
+ }
+}
diff --git a/Tests/DbLocalizationProvider.Tests/DataAnnotations/SampleViewModelWithBase.cs b/Tests/DbLocalizationProvider.Tests/DataAnnotations/SampleViewModelWithBase.cs
new file mode 100644
index 00000000..a232d026
--- /dev/null
+++ b/Tests/DbLocalizationProvider.Tests/DataAnnotations/SampleViewModelWithBase.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace DbLocalizationProvider.Tests.DataAnnotations
+{
+ [LocalizedModel(Inherited = false)]
+ public class SampleViewModelWithBase : BaseViewModel
+ {
+ [Display(Name = "Child property", Description = "")]
+ [Required]
+ [StringLength(100)]
+ public string ChildProperty { get; set; }
+ }
+}
diff --git a/Tests/DbLocalizationProvider.Tests/DataAnnotations/ViewModelWithInheritanceTests.cs b/Tests/DbLocalizationProvider.Tests/DataAnnotations/ViewModelWithInheritanceTests.cs
new file mode 100644
index 00000000..8e8b3f80
--- /dev/null
+++ b/Tests/DbLocalizationProvider.Tests/DataAnnotations/ViewModelWithInheritanceTests.cs
@@ -0,0 +1,22 @@
+using System.Linq;
+using DbLocalizationProvider.Sync;
+using Xunit;
+
+namespace DbLocalizationProvider.Tests.DataAnnotations
+{
+ public class ViewModelWithInheritanceTests
+ {
+ [Fact]
+ public void NotInheritedModel_ContainsOnlyDeclaredProperties()
+ {
+ var sut = new TypeDiscoveryHelper();
+ var properties = sut.ScanResources(typeof(SampleViewModelWithBase));
+ var keys = properties.Select(p => p.Key).ToList();
+
+ Assert.Contains("DbLocalizationProvider.Tests.DataAnnotations.SampleViewModelWithBase.ChildProperty-Description", keys);
+ Assert.DoesNotContain("DbLocalizationProvider.Tests.DataAnnotations.SampleViewModelWithBase.BaseProperty", keys);
+ Assert.DoesNotContain("DbLocalizationProvider.Tests.DataAnnotations.SampleViewModelWithBase.BaseProperty-Required", keys);
+ Assert.DoesNotContain("DbLocalizationProvider.Tests.DataAnnotations.SampleViewModelWithBase.ChildProperty-Description-Required", keys);
+ }
+ }
+}
diff --git a/Tests/DbLocalizationProvider.Tests/DbLocalizationProvider.Tests.csproj b/Tests/DbLocalizationProvider.Tests/DbLocalizationProvider.Tests.csproj
index 2a67679f..26284a2d 100644
--- a/Tests/DbLocalizationProvider.Tests/DbLocalizationProvider.Tests.csproj
+++ b/Tests/DbLocalizationProvider.Tests/DbLocalizationProvider.Tests.csproj
@@ -71,6 +71,9 @@
+
+
+
@@ -107,6 +110,7 @@
+
diff --git a/Tests/DbLocalizationProvider.Tests/DiscoveryTests/ViewModelWithIncludedOnlyTests.cs b/Tests/DbLocalizationProvider.Tests/DiscoveryTests/ViewModelWithIncludedOnlyTests.cs
index 8cf246ca..ffc70cb4 100644
--- a/Tests/DbLocalizationProvider.Tests/DiscoveryTests/ViewModelWithIncludedOnlyTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/DiscoveryTests/ViewModelWithIncludedOnlyTests.cs
@@ -6,12 +6,19 @@ namespace DbLocalizationProvider.Tests.DiscoveryTests
{
public class ViewModelWithIncludedOnlyTests
{
+ public ViewModelWithIncludedOnlyTests()
+ {
+ _sut = new TypeDiscoveryHelper();
+ }
+
+ private readonly TypeDiscoveryHelper _sut;
+
[Fact]
public void ModelWithBase_IncludedPorperty_ShouldDiscoverOnlyExplicitProperties()
{
- var properties = TypeDiscoveryHelper.GetAllProperties(typeof(SampleViewModelWithIncludedOnlyWithBase), contextAwareScanning: false)
- .Select(p => p.Key)
- .ToList();
+ var properties = _sut.ScanResources(typeof(SampleViewModelWithIncludedOnlyWithBase))
+ .Select(p => p.Key)
+ .ToList();
Assert.Contains("DbLocalizationProvider.Tests.DiscoveryTests.SampleViewModelWithIncludedOnlyWithBase.IncludedProperty", properties);
Assert.DoesNotContain("DbLocalizationProvider.Tests.DiscoveryTests.SampleViewModelWithIncludedOnlyWithBase.ExcludedProperty", properties);
@@ -23,9 +30,9 @@ public void ModelWithBase_IncludedPorperty_ShouldDiscoverOnlyExplicitProperties(
[Fact]
public void ModelWithIncludedPorperty_ShouldDiscoverOnlyExplicitProperties()
{
- var properties = TypeDiscoveryHelper.GetAllProperties(typeof(SampleViewModelWithIncludedOnly), contextAwareScanning: false)
- .Select(p => p.Key)
- .ToList();
+ var properties = _sut.ScanResources(typeof(SampleViewModelWithIncludedOnly))
+ .Select(p => p.Key)
+ .ToList();
Assert.Contains("DbLocalizationProvider.Tests.DiscoveryTests.SampleViewModelWithIncludedOnly.IncludedProperty", properties);
Assert.DoesNotContain("DbLocalizationProvider.Tests.DiscoveryTests.SampleViewModelWithIncludedOnly.ExcludedProperty", properties);
diff --git a/Tests/DbLocalizationProvider.Tests/GenericModels/GenericModelTests.cs b/Tests/DbLocalizationProvider.Tests/GenericModels/GenericModelTests.cs
index c5719119..ee7aad81 100644
--- a/Tests/DbLocalizationProvider.Tests/GenericModels/GenericModelTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/GenericModels/GenericModelTests.cs
@@ -5,10 +5,17 @@ namespace DbLocalizationProvider.Tests.GenericModels
{
public class GenericModelTests
{
+ public GenericModelTests()
+ {
+ _sut = new TypeDiscoveryHelper();
+ }
+
+ private readonly TypeDiscoveryHelper _sut;
+
[Fact]
public void TestGenericProperty()
{
- var properties = TypeDiscoveryHelper.GetAllProperties(typeof(OpenGenericModel<>), contextAwareScanning: false);
+ var properties = _sut.ScanResources(typeof(OpenGenericModel<>));
Assert.NotEmpty(properties);
}
@@ -16,9 +23,9 @@ public void TestGenericProperty()
[Fact]
public void TestGenericProperty_FromChildClass()
{
- var properties = TypeDiscoveryHelper.GetAllProperties(typeof(ClosedGenericModel), contextAwareScanning: false);
+ var properties = _sut.ScanResources(typeof(ClosedGenericModel));
Assert.NotEmpty(properties);
}
}
-}
\ No newline at end of file
+}
diff --git a/Tests/DbLocalizationProvider.Tests/InheritedModels/InheritedViewModelExpressionTests.cs b/Tests/DbLocalizationProvider.Tests/InheritedModels/InheritedViewModelExpressionTests.cs
index 71a6e6eb..6187dd85 100644
--- a/Tests/DbLocalizationProvider.Tests/InheritedModels/InheritedViewModelExpressionTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/InheritedModels/InheritedViewModelExpressionTests.cs
@@ -10,10 +10,11 @@ public class InheritedViewModelExpressionTests
[Fact]
public void Test()
{
- var properties =
- new[] { typeof(SampleViewModelWithBaseNotInherit), typeof(BaseLocalizedViewModel) }
- .Select(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false))
- .ToList();
+ var sut = new TypeDiscoveryHelper();
+
+ var properties = new[] { typeof(SampleViewModelWithBaseNotInherit), typeof(BaseLocalizedViewModel) }
+ .Select(t => sut.ScanResources(t))
+ .ToList();
var childModel = new SampleViewModelWithBaseNotInherit();
var basePropertyKey = ExpressionHelper.GetFullMemberName(() => childModel.BaseProperty);
diff --git a/Tests/DbLocalizationProvider.Tests/InheritedModels/ViewModelWithBaseTests.cs b/Tests/DbLocalizationProvider.Tests/InheritedModels/ViewModelWithBaseTests.cs
index f4bdc3be..a364462a 100644
--- a/Tests/DbLocalizationProvider.Tests/InheritedModels/ViewModelWithBaseTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/InheritedModels/ViewModelWithBaseTests.cs
@@ -9,12 +9,19 @@ namespace DbLocalizationProvider.Tests.InheritedModels
{
public class ViewModelWithBaseTests
{
+ public ViewModelWithBaseTests()
+ {
+ _sut = new TypeDiscoveryHelper();
+ }
+
+ private readonly TypeDiscoveryHelper _sut;
+
[Fact]
public void BaseProperty_HasChildClassResourceKey()
{
- var properties = TypeDiscoveryHelper.GetAllProperties(typeof(SampleViewModelWithBase), contextAwareScanning: false)
- .Select(p => p.Key)
- .ToList();
+ var properties = _sut.ScanResources(typeof(SampleViewModelWithBase))
+ .Select(p => p.Key)
+ .ToList();
Assert.Contains("DbLocalizationProvider.Tests.InheritedModels.SampleViewModelWithBase.BaseProperty", properties);
Assert.Contains("DbLocalizationProvider.Tests.InheritedModels.SampleViewModelWithBase.BaseProperty-Required", properties);
@@ -23,9 +30,9 @@ public void BaseProperty_HasChildClassResourceKey()
[Fact]
public void BaseProperty_HasChildClassResourceKey_DoesNotIncludeInheritedProperties()
{
- var properties = TypeDiscoveryHelper.GetAllProperties(typeof(SampleViewModelWithBaseNotInherit), contextAwareScanning: false)
- .Select(p => p.Key)
- .ToList();
+ var properties = _sut.ScanResources(typeof(SampleViewModelWithBaseNotInherit))
+ .Select(p => p.Key)
+ .ToList();
Assert.Contains("DbLocalizationProvider.Tests.InheritedModels.SampleViewModelWithBaseNotInherit.ChildProperty", properties);
Assert.DoesNotContain("DbLocalizationProvider.Tests.InheritedModels.SampleViewModelWithBaseNotInherit.BaseProperty", properties);
@@ -36,7 +43,7 @@ public void BuildResourceKey_ForBaseClassProperty_ExcludedFromChild_ShouldReturn
{
var properties =
new[] { typeof(SampleViewModelWithBaseNotInherit), typeof(BaseLocalizedViewModel) }
- .Select(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false))
+ .Select(t => _sut.ScanResources(t))
.ToList();
var childPropertyKey = ResourceKeyBuilder.BuildResourceKey(typeof(SampleViewModelWithBaseNotInherit), "ChildProperty");
@@ -53,7 +60,7 @@ public void BuildResourceKey_ForSecondBaseClassProperty_ExcludedFromChild_Should
{
var properties =
new[] { typeof(SampleViewModelWithBaseNotInherit), typeof(BaseLocalizedViewModel), typeof(VeryBaseLocalizedViewModel) }
- .Select(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false))
+ .Select(t => _sut.ScanResources(t))
.ToList();
var veryBasePropertyKey = ResourceKeyBuilder.BuildResourceKey(typeof(SampleViewModelWithBaseNotInherit), "VeryBaseProperty");
diff --git a/Tests/DbLocalizationProvider.Tests/LocalizedEnumTests.cs b/Tests/DbLocalizationProvider.Tests/LocalizedEnumTests.cs
index d2b06f83..ec8b4990 100644
--- a/Tests/DbLocalizationProvider.Tests/LocalizedEnumTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/LocalizedEnumTests.cs
@@ -10,10 +10,11 @@ public class LocalizedEnumTests
public LocalizedEnumTests()
{
var types = new[] { typeof(DocumentEntity) };
+ var sut = new TypeDiscoveryHelper();
Assert.NotEmpty(types);
- _properties = types.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false));
+ _properties = types.SelectMany(t => sut.ScanResources(t));
}
private readonly IEnumerable _properties;
diff --git a/Tests/DbLocalizationProvider.Tests/LocalizedEnumsDiscoveryTests.cs b/Tests/DbLocalizationProvider.Tests/LocalizedEnumsDiscoveryTests.cs
index 03aeb7d9..b4dea3ff 100644
--- a/Tests/DbLocalizationProvider.Tests/LocalizedEnumsDiscoveryTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/LocalizedEnumsDiscoveryTests.cs
@@ -19,8 +19,9 @@ public LocalizedEnumsDiscoveryTests()
[Fact]
public void DiscoverEnumValue_NameAsTranslation()
{
+ var sut = new TypeDiscoveryHelper();
var type = _types.First(t => t.FullName == "DbLocalizationProvider.Tests.SampleStatus");
- var properties = TypeDiscoveryHelper.GetAllProperties(type);
+ var properties = sut.ScanResources(type);
var openStatus = properties.First(p => p.Key == "DbLocalizationProvider.Tests.SampleStatus.Open");
diff --git a/Tests/DbLocalizationProvider.Tests/LocalizedModelsDiscoveryTests.cs b/Tests/DbLocalizationProvider.Tests/LocalizedModelsDiscoveryTests.cs
index 64b0cc5a..ea472ef8 100644
--- a/Tests/DbLocalizationProvider.Tests/LocalizedModelsDiscoveryTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/LocalizedModelsDiscoveryTests.cs
@@ -7,15 +7,26 @@ namespace DbLocalizationProvider.Tests
{
public class LocalizedModelsDiscoveryTests
{
- private readonly IEnumerable _properties;
-
public LocalizedModelsDiscoveryTests()
{
- var types = new[] { typeof(SampleViewModel), typeof(SubViewModel) };//TypeDiscoveryHelper.GetTypesWithAttribute().ToList();
+ var types = new[] { typeof(SampleViewModel), typeof(SubViewModel) };
+ var sut = new TypeDiscoveryHelper();
Assert.NotEmpty(types);
- _properties = types.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false));
+ _properties = types.SelectMany(t => sut.ScanResources(t));
+ }
+
+ private readonly IEnumerable _properties;
+
+ [Fact]
+ public void PropertyWithAttributes_DisplayDescription_Discovered()
+ {
+ var resource = _properties.FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.SampleViewModel.PropertyWithDescription");
+ Assert.NotNull(resource);
+
+ var propertyWithDescriptionResource = _properties.FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.SampleViewModel.PropertyWithDescription-Description");
+ Assert.NotNull(propertyWithDescriptionResource);
}
[Fact]
@@ -52,16 +63,5 @@ public void SingleLevel_ScalarProperties_NoAttribute()
var nullable = _properties.FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.SampleViewModel.NullableInt");
Assert.NotNull(nullable);
}
-
-
- [Fact]
- public void PropertyWithAttributes_DisplayDescription_Discovered()
- {
- var resource = _properties.FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.SampleViewModel.PropertyWithDescription");
- Assert.NotNull(resource);
-
- var propertyWithDescriptionResource = _properties.FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.SampleViewModel.PropertyWithDescription-Description");
- Assert.NotNull(propertyWithDescriptionResource);
- }
}
}
diff --git a/Tests/DbLocalizationProvider.Tests/LocalizedResourceDiscoveryTests.cs b/Tests/DbLocalizationProvider.Tests/LocalizedResourceDiscoveryTests.cs
index ee3878e2..bd0a68c2 100644
--- a/Tests/DbLocalizationProvider.Tests/LocalizedResourceDiscoveryTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/LocalizedResourceDiscoveryTests.cs
@@ -9,31 +9,21 @@ namespace DbLocalizationProvider.Tests
{
public class LocalizedResourceDiscoveryTests
{
- private readonly List _types;
-
public LocalizedResourceDiscoveryTests()
{
+ _sut = new TypeDiscoveryHelper();
_types = TypeDiscoveryHelper.GetTypesWithAttribute().ToList();
Assert.NotEmpty(_types);
}
- [Fact]
- public void SingleLevel_ScalarProperties()
- {
- var type = _types.First(t => t.FullName == "DbLocalizationProvider.Tests.ResourceKeys");
- var properties = TypeDiscoveryHelper.GetAllProperties(type);
-
- var staticField = properties.First(p => p.Key == "DbLocalizationProvider.Tests.ResourceKeys.ThisIsConstant");
-
- Assert.True(TypeDiscoveryHelper.IsStringProperty(staticField.ReturnType));
- Assert.Equal("Default value for constant", staticField.Translation);
- }
+ private readonly List _types;
+ private readonly TypeDiscoveryHelper _sut;
[Fact]
public void NestedObject_ScalarProperties()
{
var type = _types.First(t => t.FullName == "DbLocalizationProvider.Tests.ResourceKeys");
- var properties = TypeDiscoveryHelper.GetAllProperties(type).ToList();
+ var properties = _sut.ScanResources(type).ToList();
var complexPropertySubProperty = properties.FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.ResourceKeys.SubResource.SubResourceProperty");
@@ -46,7 +36,19 @@ public void NestedObject_ScalarProperties()
// need to check that there is no resource discovered for complex properties itself
Assert.DoesNotContain("DbLocalizationProvider.Tests.ResourceKeys.SubResource", properties.Select(k => k.Key));
Assert.DoesNotContain("DbLocalizationProvider.Tests.ResourceKeys.SubResource.EvenMoreComplexResource", properties.Select(k => k.Key));
+ }
+ [Fact]
+ public void NestedType_ScalarProperties()
+ {
+ var type = _types.FirstOrDefault(t => t.FullName == "DbLocalizationProvider.Tests.ParentClassForResources+ChildResourceClass");
+
+ Assert.NotNull(type);
+
+ var property = _sut.ScanResources(type).First();
+ var resourceKey = ExpressionHelper.GetFullMemberName(() => ParentClassForResources.ChildResourceClass.HelloMessage);
+
+ Assert.Equal(resourceKey, property.Key);
}
[Fact]
@@ -56,22 +58,22 @@ public void NestedType_ThroughProperty_ScalarProperties()
Assert.NotNull(type);
- var property = TypeDiscoveryHelper.GetAllProperties(type).FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.PageResources.Header.HelloMessage");
+ var property = _sut.ScanResources(type).FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.PageResources.Header.HelloMessage");
Assert.NotNull(property);
}
[Fact]
- public void NestedType_ScalarProperties()
+ public void SingleLevel_ScalarProperties()
{
- var type = _types.FirstOrDefault(t => t.FullName == "DbLocalizationProvider.Tests.ParentClassForResources+ChildResourceClass");
-
- Assert.NotNull(type);
+ var sut = new TypeDiscoveryHelper();
+ var type = _types.First(t => t.FullName == "DbLocalizationProvider.Tests.ResourceKeys");
+ var properties = sut.ScanResources(type);
- var property = TypeDiscoveryHelper.GetAllProperties(type).First();
- var resourceKey = ExpressionHelper.GetFullMemberName(() => ParentClassForResources.ChildResourceClass.HelloMessage);
+ var staticField = properties.First(p => p.Key == "DbLocalizationProvider.Tests.ResourceKeys.ThisIsConstant");
- Assert.Equal(resourceKey, property.Key);
+ Assert.True(LocalizedTypeScannerBase.IsStringProperty(staticField.ReturnType));
+ Assert.Equal("Default value for constant", staticField.Translation);
}
}
}
diff --git a/Tests/DbLocalizationProvider.Tests/NamedResources/NamedModelsTests.cs b/Tests/DbLocalizationProvider.Tests/NamedResources/NamedModelsTests.cs
index e72a65fb..a61f1641 100644
--- a/Tests/DbLocalizationProvider.Tests/NamedResources/NamedModelsTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/NamedResources/NamedModelsTests.cs
@@ -6,45 +6,25 @@ namespace DbLocalizationProvider.Tests.NamedResources
{
public class NamedModelsTests
{
- [Fact]
- public void MultipleAttributesForSingleProperty_NoPrefix()
+ public NamedModelsTests()
{
- var model = TypeDiscoveryHelper.GetTypesWithAttribute()
- .Where(t => t.FullName == $"DbLocalizationProvider.Tests.NamedResources.{nameof(ModelWithNamedProperties)}");
-
- var properties = model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false)).ToList();
-
- var nonexistingProperty = properties.FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.NamedResources.ModelWithNamedProperties.PageHeader");
- Assert.Null(nonexistingProperty);
-
- var namedProperty = properties.FirstOrDefault(p => p.Key == "/this/is/xpath/key");
- Assert.NotNull(namedProperty);
- Assert.Equal("This is page header", namedProperty.Translation);
-
- var anotherNamedProperty = properties.FirstOrDefault(p => p.Key == "/this/is/another/xpath/key");
- Assert.NotNull(anotherNamedProperty);
+ _sut = new TypeDiscoveryHelper();
+ }
- var resourceKeyOnComplexProperty = properties.FirstOrDefault(p => p.Key == "/this/is/complex/type");
- Assert.NotNull(resourceKeyOnComplexProperty);
+ private readonly TypeDiscoveryHelper _sut;
- var propertyWithDisplayName = properties.FirstOrDefault(p => p.Key == "/simple/property/with/display/name");
- Assert.NotNull(propertyWithDisplayName);
- Assert.Equal("This is simple property", propertyWithDisplayName.Translation);
+ [Fact]
+ public void DuplicateAttributes_DiffProperties_SameKey_ThrowsException()
+ {
+ var model = new[] { typeof(BadResourceWithDuplicateKeysWithinClass) };
+ Assert.Throws(() => model.SelectMany(t => _sut.ScanResources(t)).ToList());
}
[Fact]
- public void SingleAttributeForSingleProperty_WithClassPrefix()
+ public void DuplicateAttributes_SingleProperty_SameKey_ThrowsException()
{
- var model = TypeDiscoveryHelper.GetTypesWithAttribute()
- .Where(t => t.FullName == $"DbLocalizationProvider.Tests.NamedResources.{nameof(ModelWithNamedPropertiesWithPrefix)}");
-
- var properties = model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false)).ToList();
-
- var name = "/contenttypes/modelwithnamedpropertieswithprefix/properties/pageheader/name";
- var headerProperty = properties.FirstOrDefault(p => p.Key == name);
-
- Assert.NotNull(headerProperty);
- Assert.Equal("This is page header", headerProperty.Translation);
+ var model = new[] { typeof(ModelWithDuplicateResourceKeys) };
+ Assert.Throws(() => model.SelectMany(t => _sut.ScanResources(t)).ToList());
}
[Fact]
@@ -53,7 +33,7 @@ public void MultipleAttributeForSingleProperty_WithClassPrefix()
var model = TypeDiscoveryHelper.GetTypesWithAttribute()
.Where(t => t.FullName == $"DbLocalizationProvider.Tests.NamedResources.{nameof(ModelWithNamedPropertiesWithPrefix)}");
- var properties = model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false)).ToList();
+ var properties = model.SelectMany(t => _sut.ScanResources(t)).ToList();
var firstResource = properties.FirstOrDefault(p => p.Key == "/contenttypes/modelwithnamedpropertieswithprefix/resource1");
@@ -66,13 +46,39 @@ public void MultipleAttributeForSingleProperty_WithClassPrefix()
Assert.Equal("2nd resource", secondResource.Translation);
}
+ [Fact]
+ public void MultipleAttributesForSingleProperty_NoPrefix()
+ {
+ var model = TypeDiscoveryHelper.GetTypesWithAttribute()
+ .Where(t => t.FullName == $"DbLocalizationProvider.Tests.NamedResources.{nameof(ModelWithNamedProperties)}");
+
+ var properties = model.SelectMany(t => _sut.ScanResources(t)).ToList();
+
+ var nonexistingProperty = properties.FirstOrDefault(p => p.Key == "DbLocalizationProvider.Tests.NamedResources.ModelWithNamedProperties.PageHeader");
+ Assert.Null(nonexistingProperty);
+
+ var namedProperty = properties.FirstOrDefault(p => p.Key == "/this/is/xpath/key");
+ Assert.NotNull(namedProperty);
+ Assert.Equal("This is page header", namedProperty.Translation);
+
+ var anotherNamedProperty = properties.FirstOrDefault(p => p.Key == "/this/is/another/xpath/key");
+ Assert.NotNull(anotherNamedProperty);
+
+ var resourceKeyOnComplexProperty = properties.FirstOrDefault(p => p.Key == "/this/is/complex/type");
+ Assert.NotNull(resourceKeyOnComplexProperty);
+
+ var propertyWithDisplayName = properties.FirstOrDefault(p => p.Key == "/simple/property/with/display/name");
+ Assert.NotNull(propertyWithDisplayName);
+ Assert.Equal("This is simple property", propertyWithDisplayName.Translation);
+ }
+
[Fact]
public void ResourceAttributeToClass_WithClassPrefix()
{
var model = TypeDiscoveryHelper.GetTypesWithAttribute()
.Where(t => t.FullName == $"DbLocalizationProvider.Tests.NamedResources.{nameof(ModelWithNamedPropertiesWithPrefixAndKeyOnClass)}");
- var properties = model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false)).ToList();
+ var properties = model.SelectMany(t => _sut.ScanResources(t)).ToList();
var firstResource = properties.FirstOrDefault(p => p.Key == "/contenttypes/modelwithnamedpropertieswithprefixandkeyonclass/name");
Assert.NotNull(firstResource);
@@ -85,17 +91,18 @@ public void ResourceAttributeToClass_WithClassPrefix()
}
[Fact]
- public void DuplicateAttributes_SingleProperty_SameKey_ThrowsException()
+ public void SingleAttributeForSingleProperty_WithClassPrefix()
{
- var model = new[] { typeof(ModelWithDuplicateResourceKeys) };
- Assert.Throws(() => model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false)).ToList());
- }
+ var model = TypeDiscoveryHelper.GetTypesWithAttribute()
+ .Where(t => t.FullName == $"DbLocalizationProvider.Tests.NamedResources.{nameof(ModelWithNamedPropertiesWithPrefix)}");
- [Fact]
- public void DuplicateAttributes_DiffProperties_SameKey_ThrowsException()
- {
- var model = new[] { typeof(BadResourceWithDuplicateKeysWithinClass) };
- Assert.Throws(() => model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t, contextAwareScanning: false)).ToList());
+ var properties = model.SelectMany(t => _sut.ScanResources(t)).ToList();
+
+ var name = "/contenttypes/modelwithnamedpropertieswithprefix/properties/pageheader/name";
+ var headerProperty = properties.FirstOrDefault(p => p.Key == name);
+
+ Assert.NotNull(headerProperty);
+ Assert.Equal("This is page header", headerProperty.Translation);
}
}
}
diff --git a/Tests/DbLocalizationProvider.Tests/NamedResources/NamedResourcesTests.cs b/Tests/DbLocalizationProvider.Tests/NamedResources/NamedResourcesTests.cs
index 797b2e78..dcfac3e0 100644
--- a/Tests/DbLocalizationProvider.Tests/NamedResources/NamedResourcesTests.cs
+++ b/Tests/DbLocalizationProvider.Tests/NamedResources/NamedResourcesTests.cs
@@ -7,18 +7,25 @@ namespace DbLocalizationProvider.Tests.NamedResources
{
public class NamedResourcesTests
{
+ public NamedResourcesTests()
+ {
+ _sut = new TypeDiscoveryHelper();
+ }
+
+ private readonly TypeDiscoveryHelper _sut;
+
[Fact]
public void DuplicateAttributes_DiffProperties_SameKey_ThrowsException()
{
var model = new[] { typeof(BadResourceWithDuplicateKeysWithinClass) };
- Assert.Throws(() => model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t)).ToList());
+ Assert.Throws(() => model.SelectMany(t => _sut.ScanResources(t)).ToList());
}
[Fact]
public void DuplicateAttributes_SingleProperty_SameKey_ThrowsException()
{
var model = new[] { typeof(BadResourceWithDuplicateKeys) };
- Assert.Throws(() => model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t)).ToList());
+ Assert.Throws(() => model.SelectMany(t => _sut.ScanResources(t)).ToList());
}
[Fact]
@@ -43,7 +50,7 @@ public void MultipleAttributesForSingleProperty_NoPrefix()
var model = TypeDiscoveryHelper.GetTypesWithAttribute()
.Where(t => t.FullName == $"DbLocalizationProvider.Tests.NamedResources.{nameof(ResourcesWithNamedKeys)}");
- var properties = model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t)).ToList();
+ var properties = model.SelectMany(t => _sut.ScanResources(t)).ToList();
var namedResource = properties.FirstOrDefault(p => p.Key == "/this/is/xpath/to/resource");
@@ -57,7 +64,7 @@ public void MultipleAttributesForSingleProperty_WithPrefix()
var model = TypeDiscoveryHelper.GetTypesWithAttribute()
.Where(t => t.FullName == $"DbLocalizationProvider.Tests.NamedResources.{nameof(ResourcesWithNamedKeysWithPrefix)}");
- var properties = model.SelectMany(t => TypeDiscoveryHelper.GetAllProperties(t)).ToList();
+ var properties = model.SelectMany(t => _sut.ScanResources(t)).ToList();
var namedResource = properties.FirstOrDefault(p => p.Key == "/this/is/root/resource/and/this/is/header");
diff --git a/Tests/DbLocalizationProvider.Tests/ScannerTests/TypeScannerTests.cs b/Tests/DbLocalizationProvider.Tests/ScannerTests/TypeScannerTests.cs
new file mode 100644
index 00000000..1392a797
--- /dev/null
+++ b/Tests/DbLocalizationProvider.Tests/ScannerTests/TypeScannerTests.cs
@@ -0,0 +1,18 @@
+using DbLocalizationProvider.Sync;
+using Xunit;
+
+namespace DbLocalizationProvider.Tests.ScannerTests
+{
+ public class TypeScannerTests
+ {
+ [Fact]
+ public void ViewModelType_ShouldSelectModelScanner()
+ {
+ var sut = new LocalizedModelTypeScanner();
+
+ var result = sut.ShouldScan(typeof(SampleViewModel));
+
+ Assert.True(result);
+ }
+ }
+}
diff --git a/src/DbLocalizationProvider/DataAnnotations/LocalizedMetadataProvider.cs b/src/DbLocalizationProvider/DataAnnotations/LocalizedMetadataProvider.cs
index d0dc3208..888dc06b 100644
--- a/src/DbLocalizationProvider/DataAnnotations/LocalizedMetadataProvider.cs
+++ b/src/DbLocalizationProvider/DataAnnotations/LocalizedMetadataProvider.cs
@@ -41,7 +41,7 @@ protected override ModelMetadata CreateMetadata(
}
var displayAttribute = theAttributes.OfType().FirstOrDefault();
- if(!string.IsNullOrEmpty(displayAttribute?.Description))
+ if(displayAttribute?.Description != null)
{
data.Description = ModelMetadataLocalizationHelper.GetTranslation(containerType, $"{propertyName}-Description");
}
diff --git a/src/DbLocalizationProvider/DbLocalizationProvider.csproj b/src/DbLocalizationProvider/DbLocalizationProvider.csproj
index 0166e516..73b1dad1 100644
--- a/src/DbLocalizationProvider/DbLocalizationProvider.csproj
+++ b/src/DbLocalizationProvider/DbLocalizationProvider.csproj
@@ -150,6 +150,10 @@
+
+
+
+
diff --git a/src/DbLocalizationProvider/Properties/AssemblyInfo.cs b/src/DbLocalizationProvider/Properties/AssemblyInfo.cs
index be764a5e..8ffc9983 100644
--- a/src/DbLocalizationProvider/Properties/AssemblyInfo.cs
+++ b/src/DbLocalizationProvider/Properties/AssemblyInfo.cs
@@ -5,9 +5,9 @@
[assembly: AssemblyTitle("DbLocalizationProvider")]
[assembly: AssemblyDescription("Database driven localization provider")]
[assembly: Guid("17ca5d23-46c3-44b1-8fa6-0f40b2e447ba")]
-[assembly: AssemblyVersion("2.1.0.0")]
-[assembly: AssemblyFileVersion("2.1.0.0")]
-[assembly: AssemblyInformationalVersion("2.1.0")]
+[assembly: AssemblyVersion("2.1.1.0")]
+[assembly: AssemblyFileVersion("2.1.1.0")]
+[assembly: AssemblyInformationalVersion("2.1.1")]
[assembly: InternalsVisibleTo("DbLocalizationProvider.EPiServer")]
[assembly: InternalsVisibleTo("DbLocalizationProvider.AdminUI")]
[assembly: InternalsVisibleTo("DbLocalizationProvider.MigrationTool")]
diff --git a/src/DbLocalizationProvider/Sync/IResourceTypeScanner.cs b/src/DbLocalizationProvider/Sync/IResourceTypeScanner.cs
new file mode 100644
index 00000000..92155244
--- /dev/null
+++ b/src/DbLocalizationProvider/Sync/IResourceTypeScanner.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+
+namespace DbLocalizationProvider.Sync
+{
+ internal interface IResourceTypeScanner
+ {
+ bool ShouldScan(Type target);
+ string GetResourceKeyPrefix(Type target, string keyPrefix = null);
+ ICollection GetClassLevelResources(Type target, string resourceKeyPrefix);
+ ICollection GetResources(Type target, string resourceKeyPrefix);
+ }
+}
\ No newline at end of file
diff --git a/src/DbLocalizationProvider/Sync/LocalizedModelTypeScanner.cs b/src/DbLocalizationProvider/Sync/LocalizedModelTypeScanner.cs
new file mode 100644
index 00000000..4bbf4ab7
--- /dev/null
+++ b/src/DbLocalizationProvider/Sync/LocalizedModelTypeScanner.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using DbLocalizationProvider.Internal;
+
+namespace DbLocalizationProvider.Sync
+{
+ internal class LocalizedModelTypeScanner : LocalizedTypeScannerBase, IResourceTypeScanner
+ {
+ public bool ShouldScan(Type target)
+ {
+ return target.GetCustomAttribute() != null;
+ }
+
+ public string GetResourceKeyPrefix(Type target, string keyPrefix = null)
+ {
+ var modelAttribute = target.GetCustomAttribute();
+
+ return !string.IsNullOrEmpty(modelAttribute?.KeyPrefix) ? modelAttribute.KeyPrefix : target.FullName;
+ }
+
+ public ICollection GetClassLevelResources(Type target, string resourceKeyPrefix)
+ {
+ var result = new List();
+ var resourceAttributesOnModelClass = target.GetCustomAttributes().ToList();
+ if(!resourceAttributesOnModelClass.Any())
+ return result;
+
+ foreach (var resourceKeyAttribute in resourceAttributesOnModelClass)
+ {
+ result.Add(new DiscoveredResource(null,
+ ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, resourceKeyAttribute.Key, separator: string.Empty),
+ null,
+ resourceKeyAttribute.Value,
+ target,
+ typeof(string),
+ true));
+ }
+
+ return result;
+ }
+
+ public ICollection GetResources(Type target, string resourceKeyPrefix)
+ {
+ var resourceSources = GetResourceSources(target);
+ var attr = target.GetCustomAttribute();
+ var isKeyPrefixSpecified = !string.IsNullOrEmpty(attr?.KeyPrefix);
+
+ return resourceSources.SelectMany(pi => DiscoverResourcesFromProperty(pi, resourceKeyPrefix, isKeyPrefixSpecified)).ToList();
+ }
+
+ private ICollection GetResourceSources(Type target)
+ {
+ var modelAttribute = target.GetCustomAttribute();
+ if(modelAttribute == null)
+ return new List();
+
+ var flags = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Static;
+
+ if(!modelAttribute.Inherited)
+ flags = flags | BindingFlags.DeclaredOnly;
+
+ return target.GetProperties(flags)
+ .Where(pi => pi.GetCustomAttribute() == null)
+ .Where(pi => !modelAttribute.OnlyIncluded || pi.GetCustomAttribute() != null).ToList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DbLocalizationProvider/Sync/LocalizedResourceTypeScanner.cs b/src/DbLocalizationProvider/Sync/LocalizedResourceTypeScanner.cs
new file mode 100644
index 00000000..3384e1c1
--- /dev/null
+++ b/src/DbLocalizationProvider/Sync/LocalizedResourceTypeScanner.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using DbLocalizationProvider.Internal;
+
+namespace DbLocalizationProvider.Sync
+{
+ internal class LocalizedResourceTypeScanner : LocalizedTypeScannerBase, IResourceTypeScanner
+ {
+ public bool ShouldScan(Type target)
+ {
+ return target.GetCustomAttribute() != null;
+ }
+
+ public string GetResourceKeyPrefix(Type target, string keyPrefix = null)
+ {
+ var resourceAttribute = target.GetCustomAttribute();
+
+ return !string.IsNullOrEmpty(resourceAttribute?.KeyPrefix)
+ ? resourceAttribute.KeyPrefix
+ : (string.IsNullOrEmpty(keyPrefix) ? target.FullName : keyPrefix);
+ }
+
+ public ICollection GetClassLevelResources(Type target, string resourceKeyPrefix)
+ {
+ return new List();
+ }
+
+ public ICollection GetResources(Type target, string resourceKeyPrefix)
+ {
+ if(target.BaseType == typeof(Enum))
+ {
+ return target.GetMembers(BindingFlags.Public | BindingFlags.Static)
+ .Select(mi => new DiscoveredResource(mi,
+ ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, mi),
+ mi.Name,
+ mi.Name,
+ target,
+ Enum.GetUnderlyingType(target),
+ Enum.GetUnderlyingType(target).IsSimpleType())).ToList();
+ }
+
+ var resourceSources = GetResourceSources(target);
+ var attr = target.GetCustomAttribute();
+ var isKeyPrefixSpecified = !string.IsNullOrEmpty(attr?.KeyPrefix);
+
+ return resourceSources.SelectMany(pi => DiscoverResourcesFromProperty(pi, resourceKeyPrefix, isKeyPrefixSpecified)).ToList();
+ }
+
+ private ICollection GetResourceSources(Type target)
+ {
+ var flags = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Static;
+
+ return target.GetProperties(flags)
+ .Where(pi => pi.GetCustomAttribute() == null).ToList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DbLocalizationProvider/Sync/LocalizedTypeScannerBase.cs b/src/DbLocalizationProvider/Sync/LocalizedTypeScannerBase.cs
new file mode 100644
index 00000000..160ce2ea
--- /dev/null
+++ b/src/DbLocalizationProvider/Sync/LocalizedTypeScannerBase.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Reflection;
+using DbLocalizationProvider.Internal;
+
+namespace DbLocalizationProvider.Sync
+{
+ internal abstract class LocalizedTypeScannerBase
+ {
+ protected IEnumerable DiscoverResourcesFromProperty(PropertyInfo pi, string resourceKeyPrefix, bool typeKeyPrefixSpecified)
+ {
+ // check if there are [ResourceKey] attributes
+ var keyAttributes = pi.GetCustomAttributes().ToList();
+ var resourceKey = ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, pi);
+ var translation = GetResourceValue(pi);
+
+ if(!keyAttributes.Any())
+ {
+ yield return new DiscoveredResource(pi,
+ resourceKey,
+ translation,
+ pi.Name,
+ pi.PropertyType,
+ pi.GetMethod.ReturnType,
+ pi.GetMethod.ReturnType.IsSimpleType());
+
+ // try to fetch also [Display()] attribute to generate new "...-Description" resource => usually used for help text labels
+ var displayAttribute = pi.GetCustomAttribute();
+ if(displayAttribute?.Description != null)
+ {
+ yield return new DiscoveredResource(pi,
+ $"{resourceKey}-Description",
+ displayAttribute.Description,
+ $"{pi.Name}-Description",
+ pi.PropertyType,
+ pi.GetMethod.ReturnType,
+ pi.GetMethod.ReturnType.IsSimpleType());
+ }
+
+ var validationAttributes = pi.GetCustomAttributes();
+ foreach (var validationAttribute in validationAttributes)
+ {
+ var validationResourceKey = ResourceKeyBuilder.BuildResourceKey(resourceKey, validationAttribute);
+ var propertyName = validationResourceKey.Split('.').Last();
+ yield return new DiscoveredResource(pi,
+ validationResourceKey,
+ string.IsNullOrEmpty(validationAttribute.ErrorMessage) ? propertyName : validationAttribute.ErrorMessage,
+ propertyName,
+ pi.PropertyType,
+ pi.GetMethod.ReturnType,
+ pi.GetMethod.ReturnType.IsSimpleType());
+ }
+ }
+
+ foreach (var resourceKeyAttribute in keyAttributes)
+ {
+ yield return new DiscoveredResource(pi,
+ ResourceKeyBuilder.BuildResourceKey(typeKeyPrefixSpecified ? resourceKeyPrefix : null,
+ resourceKeyAttribute.Key,
+ separator: string.Empty),
+ string.IsNullOrEmpty(resourceKeyAttribute.Value) ? translation : resourceKeyAttribute.Value,
+ null,
+ pi.PropertyType,
+ pi.GetMethod.ReturnType,
+ true);
+ }
+ }
+
+ private static string GetResourceValue(PropertyInfo pi)
+ {
+ var result = pi.Name;
+
+ // try to extract resource value
+ var methodInfo = pi.GetGetMethod();
+ if(IsStringProperty(methodInfo.ReturnType))
+ {
+ try
+ {
+ if(methodInfo.IsStatic)
+ {
+ result = methodInfo.Invoke(null, null) as string;
+ }
+ else
+ {
+ if(pi.DeclaringType != null)
+ {
+ var targetInstance = Activator.CreateInstance(pi.DeclaringType);
+ var propertyReturnValue = methodInfo.Invoke(targetInstance, null) as string;
+ if(propertyReturnValue != null)
+ {
+ result = propertyReturnValue;
+ }
+ }
+ }
+ }
+ catch
+ {
+ // if we fail to retrieve value for the resource - fair enough
+ }
+ }
+
+ var attributes = pi.GetCustomAttributes(true);
+ var displayAttribute = attributes.OfType().FirstOrDefault();
+
+ if(!string.IsNullOrEmpty(displayAttribute?.GetName()))
+ {
+ result = displayAttribute.GetName();
+ }
+
+ var displayNameAttribute = attributes.OfType().FirstOrDefault();
+ if(!string.IsNullOrEmpty(displayNameAttribute?.DisplayName))
+ {
+ result = displayNameAttribute.DisplayName;
+ }
+
+ return result;
+ }
+
+ internal static bool IsStringProperty(Type returnType)
+ {
+ return returnType == typeof(string);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DbLocalizationProvider/Sync/ResourceSynchronizer.cs b/src/DbLocalizationProvider/Sync/ResourceSynchronizer.cs
index 81e7329d..e403ab61 100644
--- a/src/DbLocalizationProvider/Sync/ResourceSynchronizer.cs
+++ b/src/DbLocalizationProvider/Sync/ResourceSynchronizer.cs
@@ -81,7 +81,8 @@ private void ResetSyncStatus(DbContext db)
private void RegisterDiscoveredModels(LanguageEntities db, IEnumerable types)
{
- var properties = types.SelectMany(type => TypeDiscoveryHelper.GetAllProperties(type, contextAwareScanning: false));
+ var sut = new TypeDiscoveryHelper();
+ var properties = types.SelectMany(type => sut.ScanResources(type));
foreach (var property in properties)
{
@@ -92,7 +93,8 @@ private void RegisterDiscoveredModels(LanguageEntities db, IEnumerable typ
private void RegisterDiscoveredResources(LanguageEntities db, IEnumerable types)
{
- var properties = types.SelectMany(type => TypeDiscoveryHelper.GetAllProperties(type));
+ var sut = new TypeDiscoveryHelper();
+ var properties = types.SelectMany(type => sut.ScanResources(type));
foreach (var property in properties)
{
diff --git a/src/DbLocalizationProvider/Sync/TypeDiscoveryHelper.cs b/src/DbLocalizationProvider/Sync/TypeDiscoveryHelper.cs
index 5ea73fb5..d182258a 100644
--- a/src/DbLocalizationProvider/Sync/TypeDiscoveryHelper.cs
+++ b/src/DbLocalizationProvider/Sync/TypeDiscoveryHelper.cs
@@ -1,17 +1,60 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.ComponentModel;
-using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
-using DbLocalizationProvider.Internal;
namespace DbLocalizationProvider.Sync
{
internal class TypeDiscoveryHelper
{
internal static ConcurrentDictionary> DiscoveredResourceCache = new ConcurrentDictionary>();
+ private readonly List _scanners = new List();
+
+ public TypeDiscoveryHelper()
+ {
+ _scanners.Add(new LocalizedModelTypeScanner());
+ _scanners.Add(new LocalizedResourceTypeScanner());
+ }
+
+ public IEnumerable ScanResources(Type target, string keyPrefix = null, IResourceTypeScanner scanner = null)
+ {
+ var typeScanner = scanner;
+
+ if(scanner == null)
+ typeScanner = _scanners.FirstOrDefault(s => s.ShouldScan(target));
+
+ if(typeScanner == null)
+ return new List();
+
+ if (target.IsGenericParameter)
+ return new List();
+
+ var resourceKeyPrefix = typeScanner.GetResourceKeyPrefix(target, keyPrefix);
+
+ var buffer = new List();
+ buffer.AddRange(typeScanner.GetClassLevelResources(target, resourceKeyPrefix));
+
+ buffer.AddRange(typeScanner.GetResources(target, resourceKeyPrefix));
+
+ var result = buffer.Where(t => t.IsSimpleType || t.Info == null || t.Info.GetCustomAttribute() != null)
+ .ToList();
+
+ foreach (var property in buffer.Where(t => !t.IsSimpleType))
+ {
+ if(!property.IsSimpleType)
+ result.AddRange(ScanResources(property.DeclaringType, property.Key, typeScanner));
+ }
+
+ var duplicateKeys = result.GroupBy(r => r.Key).Where(g => g.Count() > 1).ToList();
+ if(duplicateKeys.Any())
+ throw new DuplicateResourceKeyException($"Duplicate keys: [{string.Join(", ", duplicateKeys.Select(g => g.Key))}]");
+
+ // add scanned resources to the cache
+ DiscoveredResourceCache.TryAdd(target.FullName, result.Where(r => !string.IsNullOrEmpty(r.PropertyName)).Select(r => r.PropertyName).ToList());
+
+ return result;
+ }
internal static List> GetTypes(params Func[] filters)
{
@@ -62,126 +105,122 @@ internal static IEnumerable GetTypesChildOf()
return allTypes;
}
- internal static IEnumerable GetAllProperties(Type type, string keyPrefix = null, bool contextAwareScanning = true)
- {
- var resourceKeyPrefix = type.FullName;
- var typeKeyPrefixSpecified = false;
- var properties = new List();
- var modelAttribute = type.GetCustomAttribute();
-
- if(contextAwareScanning)
- {
- // this is resource class scanning - try to fetch resource key prefix attribute if set there
- var resourceAttribute = type.GetCustomAttribute();
- if(!string.IsNullOrEmpty(resourceAttribute?.KeyPrefix))
- {
- resourceKeyPrefix = resourceAttribute.KeyPrefix;
- typeKeyPrefixSpecified = true;
- }
- else
- {
- resourceKeyPrefix = string.IsNullOrEmpty(keyPrefix) ? type.FullName : keyPrefix;
- }
- }
- else
- {
- // this is model scanning - try to fetch resource key prefix attribute if set there
- if(!string.IsNullOrEmpty(modelAttribute?.KeyPrefix))
- {
- resourceKeyPrefix = modelAttribute.KeyPrefix;
- typeKeyPrefixSpecified = true;
- }
-
- var resourceAttributesOnModelClass = type.GetCustomAttributes().ToList();
- if(resourceAttributesOnModelClass.Any())
- {
- foreach (var resourceKeyAttribute in resourceAttributesOnModelClass)
- {
- properties.Add(new DiscoveredResource(null,
- ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, resourceKeyAttribute.Key, separator: string.Empty),
- null,
- resourceKeyAttribute.Value,
- type,
- typeof(string),
- true));
- }
- }
- }
-
- if(type.BaseType == typeof(Enum))
- {
- properties.AddRange(type.GetMembers(BindingFlags.Public | BindingFlags.Static)
- .Select(mi => new DiscoveredResource(mi,
- ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, mi),
- mi.Name,
- mi.Name,
- type,
- Enum.GetUnderlyingType(type),
- Enum.GetUnderlyingType(type).IsSimpleType())).ToList());
- }
- else
- {
- var flags = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Static;
- if(modelAttribute != null && !modelAttribute.Inherited)
- flags = flags | BindingFlags.DeclaredOnly;
-
- properties.AddRange(type.GetProperties(flags)
- .Where(pi => pi.GetCustomAttribute() == null)
- .Where(pi => modelAttribute == null || !modelAttribute.OnlyIncluded || pi.GetCustomAttribute() != null)
- .SelectMany(pi => DiscoverResourcesFromProperty(pi, resourceKeyPrefix, typeKeyPrefixSpecified)).ToList());
- }
-
- var duplicateKeys = properties.GroupBy(r => r.Key).Where(g => g.Count() > 1).ToList();
- if(duplicateKeys.Any())
- {
- throw new DuplicateResourceKeyException($"Duplicate keys: [{string.Join(", ", duplicateKeys.Select(g => g.Key))}]");
- }
-
- // first we can filter out all simple and/or complex included properties from the type as starting list of discovered resources
- var results = new List(properties.Where(t => t.IsSimpleType || t.Info == null || t.Info.GetCustomAttribute() != null));
-
- foreach (var property in properties)
- {
- var pi = property.Info;
- var deeperModelType = property.ReturnType;
-
- if(!property.IsSimpleType)
- {
- // if this is not a simple type - we need to scan deeper only if deeper model has attribute annotation
- if(contextAwareScanning || deeperModelType.GetCustomAttribute() != null)
- {
- results.AddRange(GetAllProperties(property.DeclaringType, property.Key, contextAwareScanning));
- }
- }
-
- if(pi == null)
- continue;
-
- var validationAttributes = pi.GetCustomAttributes();
- foreach (var validationAttribute in validationAttributes)
- {
- var resourceKey = ResourceKeyBuilder.BuildResourceKey(property.Key, validationAttribute);
- var propertyName = resourceKey.Split('.').Last();
- results.Add(new DiscoveredResource(pi,
- resourceKey,
- string.IsNullOrEmpty(validationAttribute.ErrorMessage) ? propertyName : validationAttribute.ErrorMessage,
- propertyName,
- property.DeclaringType,
- property.ReturnType,
- property.ReturnType.IsSimpleType()));
- }
- }
-
- // add scanned resources to the cache
- DiscoveredResourceCache.TryAdd(type.FullName, results.Where(r => !string.IsNullOrEmpty(r.PropertyName)).Select(r => r.PropertyName).ToList());
-
- return results;
- }
-
- internal static bool IsStringProperty(Type returnType)
- {
- return returnType == typeof(string);
- }
+ //internal static IEnumerable GetAllProperties(Type type, string keyPrefix = null, bool contextAwareScanning = true)
+ //{
+ // var resourceKeyPrefix = type.FullName;
+ // var typeKeyPrefixSpecified = false;
+ // var properties = new List();
+ // var modelAttribute = type.GetCustomAttribute();
+
+ // //if(contextAwareScanning)
+ // //{
+ // // this is resource class scanning - try to fetch resource key prefix attribute if set there
+ // //var resourceAttribute = type.GetCustomAttribute();
+ // //if(!string.IsNullOrEmpty(resourceAttribute?.KeyPrefix))
+ // //{
+ // // resourceKeyPrefix = resourceAttribute.KeyPrefix;
+ // // typeKeyPrefixSpecified = true;
+ // //}
+ // //else
+ // //{
+ // // resourceKeyPrefix = string.IsNullOrEmpty(keyPrefix) ? type.FullName : keyPrefix;
+ // //}
+ // //}
+ // //else
+ // //{
+ // // this is model scanning - try to fetch resource key prefix attribute if set there
+ // //if(!string.IsNullOrEmpty(modelAttribute?.KeyPrefix))
+ // //{
+ // // resourceKeyPrefix = modelAttribute.KeyPrefix;
+ // // typeKeyPrefixSpecified = true;
+ // //}
+
+ // //var resourceAttributesOnModelClass = type.GetCustomAttributes().ToList();
+ // //if(resourceAttributesOnModelClass.Any())
+ // //{
+ // // foreach (var resourceKeyAttribute in resourceAttributesOnModelClass)
+ // // {
+ // // properties.Add(new DiscoveredResource(null,
+ // // ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, resourceKeyAttribute.Key, separator: string.Empty),
+ // // null,
+ // // resourceKeyAttribute.Value,
+ // // type,
+ // // typeof(string),
+ // // true));
+ // // }
+ // //}
+ // //}
+
+ // //if(type.BaseType == typeof(Enum))
+ // //{
+ // // properties.AddRange(type.GetMembers(BindingFlags.Public | BindingFlags.Static)
+ // // .Select(mi => new DiscoveredResource(mi,
+ // // ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, mi),
+ // // mi.Name,
+ // // mi.Name,
+ // // type,
+ // // Enum.GetUnderlyingType(type),
+ // // Enum.GetUnderlyingType(type).IsSimpleType())).ToList());
+ // //}
+ // //else
+ // //{
+ // // var flags = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Static;
+ // // if (modelAttribute != null && !modelAttribute.Inherited)
+ // // flags = flags | BindingFlags.DeclaredOnly;
+
+ // // properties.AddRange(type.GetProperties(flags)
+ // // .Where(pi => pi.GetCustomAttribute() == null)
+ // // .Where(pi => modelAttribute == null || !modelAttribute.OnlyIncluded || pi.GetCustomAttribute() != null)
+ // // .SelectMany(pi => DiscoverResourcesFromProperty(pi, resourceKeyPrefix, typeKeyPrefixSpecified)).ToList());
+ // //}
+
+ // //var duplicateKeys = properties.GroupBy(r => r.Key).Where(g => g.Count() > 1).ToList();
+ // //if(duplicateKeys.Any())
+ // //{
+ // // throw new DuplicateResourceKeyException($"Duplicate keys: [{string.Join(", ", duplicateKeys.Select(g => g.Key))}]");
+ // //}
+
+ // // first we can filter out all simple and/or complex included properties from the type as starting list of discovered resources
+
+ // var results = new List(properties.Where(t => t.IsSimpleType || t.Info == null || t.Info.GetCustomAttribute() != null));
+
+ // //foreach (var property in properties)
+ // //{
+ // // var pi = property.Info;
+ // // var deeperModelType = property.ReturnType;
+
+ // // if(!property.IsSimpleType)
+ // // {
+ // // // if this is not a simple type - we need to scan deeper only if deeper model has attribute annotation
+ // // if(contextAwareScanning || deeperModelType.GetCustomAttribute() != null)
+ // // {
+ // // results.AddRange(GetAllProperties(property.DeclaringType, property.Key, contextAwareScanning));
+ // // }
+ // // }
+
+ // // if(pi == null)
+ // // continue;
+
+ // // var validationAttributes = pi.GetCustomAttributes();
+ // // foreach (var validationAttribute in validationAttributes)
+ // // {
+ // // var resourceKey = ResourceKeyBuilder.BuildResourceKey(property.Key, validationAttribute);
+ // // var propertyName = resourceKey.Split('.').Last();
+ // // results.Add(new DiscoveredResource(pi,
+ // // resourceKey,
+ // // string.IsNullOrEmpty(validationAttribute.ErrorMessage) ? propertyName : validationAttribute.ErrorMessage,
+ // // propertyName,
+ // // property.DeclaringType,
+ // // property.ReturnType,
+ // // property.ReturnType.IsSimpleType()));
+ // // }
+ // //}
+
+ // // add scanned resources to the cache
+ // //DiscoveredResourceCache.TryAdd(type.FullName, results.Where(r => !string.IsNullOrEmpty(r.PropertyName)).Select(r => r.PropertyName).ToList());
+
+ // return results;
+ //}
private static IEnumerable GetAssemblies()
{
@@ -208,99 +247,5 @@ private static IEnumerable SelectTypes(Assembly assembly, Func
return new List();
}
}
-
- private static IEnumerable DiscoverResourcesFromProperty(PropertyInfo pi, string resourceKeyPrefix, bool typeKeyPrefixSpecified)
- {
- // check if there are [ResourceKey] attributes
- var keyAttributes = pi.GetCustomAttributes().ToList();
- var translation = GetResourceValue(pi);
-
- if(!keyAttributes.Any())
- {
- yield return new DiscoveredResource(pi,
- ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, pi),
- translation,
- pi.Name,
- pi.PropertyType,
- pi.GetMethod.ReturnType,
- pi.GetMethod.ReturnType.IsSimpleType());
-
- // try to fetch also [Display()] attribute to generate new "...-Description" resource => usually used for help text labels
- var displayAttribute = pi.GetCustomAttribute();
- if(!string.IsNullOrEmpty(displayAttribute?.Description))
- {
- yield return new DiscoveredResource(pi,
- $"{ResourceKeyBuilder.BuildResourceKey(resourceKeyPrefix, pi)}-Description",
- $"{pi.Name}-Description",
- displayAttribute.Description,
- pi.PropertyType,
- pi.GetMethod.ReturnType,
- pi.GetMethod.ReturnType.IsSimpleType());
- }
- }
-
- foreach (var resourceKeyAttribute in keyAttributes)
- {
- yield return new DiscoveredResource(pi,
- ResourceKeyBuilder.BuildResourceKey(typeKeyPrefixSpecified ? resourceKeyPrefix : null,
- resourceKeyAttribute.Key,
- separator: string.Empty),
- string.IsNullOrEmpty(resourceKeyAttribute.Value) ? translation : resourceKeyAttribute.Value,
- null,
- pi.PropertyType,
- pi.GetMethod.ReturnType,
- true);
- }
- }
-
- private static string GetResourceValue(PropertyInfo pi)
- {
- var result = pi.Name;
-
- // try to extract resource value
- var methodInfo = pi.GetGetMethod();
- if(IsStringProperty(methodInfo.ReturnType))
- {
- try
- {
- if(methodInfo.IsStatic)
- {
- result = methodInfo.Invoke(null, null) as string;
- }
- else
- {
- if(pi.DeclaringType != null)
- {
- var targetInstance = Activator.CreateInstance(pi.DeclaringType);
- var propertyReturnValue = methodInfo.Invoke(targetInstance, null) as string;
- if(propertyReturnValue != null)
- {
- result = propertyReturnValue;
- }
- }
- }
- }
- catch
- {
- // if we fail to retrieve value for the resource - fair enough
- }
- }
-
- var attributes = pi.GetCustomAttributes(true);
- var displayAttribute = attributes.OfType().FirstOrDefault();
-
- if(!string.IsNullOrEmpty(displayAttribute?.GetName()))
- {
- result = displayAttribute.GetName();
- }
-
- var displayNameAttribute = attributes.OfType().FirstOrDefault();
- if(!string.IsNullOrEmpty(displayNameAttribute?.DisplayName))
- {
- result = displayNameAttribute.DisplayName;
- }
-
- return result;
- }
}
}