Browse Source

Merge pull request #3561 from abpframework/validating-extra-props

Implement validation for extra properties of the extensible objects
pull/3563/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
c49b06cdc8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 112
      docs/en/Object-Extensions.md
  2. 18
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs
  3. 13
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
  4. 2
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs
  5. 11
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs
  6. 200
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs
  7. 4
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs
  8. 9
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  9. 61
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs
  10. 53
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs
  11. 1
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs
  12. 172
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs
  13. 4
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs

112
docs/en/Object-Extensions.md

@ -156,11 +156,9 @@ ObjectExtensionManager.Instance
);
````
#### Property Configuration
### Property Configuration
`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition.
Example:
`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition:
````csharp
ObjectExtensionManager.Instance
@ -168,13 +166,113 @@ ObjectExtensionManager.Instance
"SocialSecurityNumber",
options =>
{
options.CheckPairDefinitionOnMapping = false;
//Configure options...
});
````
> `options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document.
The following sections explain the fundamental property configuration options.
#### CheckPairDefinitionOnMapping
Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better.
## Validation
You may want to add some **validation rules** for the extra properties you've defined. `AddOrUpdateProperty` method options allows two ways of performing validation:
1. You can add **data annotation attributes** for a property.
2. You can write an action (code block) to perform a **custom validation**.
Validation works when you use the object in a method that is **automatically validated** (e.g. controller actions, page handler methods, application service methods...). So, all extra properties are validated whenever the extended object is being validated.
### Data Annotation Attributes
All of the standard data annotation attributes are valid for extra properties. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.ValidationAttributes.Add(new RequiredAttribute());
options.ValidationAttributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
);
});
````
With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided.
### Custom Validation
If you need, you can add a custom action that is executed to validate the extra properties. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Validators.Add(context =>
{
var socialSecurityNumber = context.Value as string;
if (socialSecurityNumber == null ||
socialSecurityNumber.StartsWith("X"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Invalid social security number: " + socialSecurityNumber,
new[] { "SocialSecurityNumber" }
)
);
}
});
});
````
> See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option.
`context.ServiceProvider` can be used to resolve a service dependency for advanced scenarios.
In addition to add custom validation logic for a single property, you can add a custom validation logic that is executed in object level. Example:
`options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document.
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUserCreateDto>(objConfig =>
{
//Define two properties with their own validation rules
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
//Write a common validation logic works on multiple properties
objConfig.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.ValidatingObject.GetProperty<string>("PasswordRepeat"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Please repeat the same password!",
new[] { "Password", "PasswordRepeat" }
)
);
}
});
});
````
## Object to Object Mapping

18
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Domain.Entities
{
@ -56,6 +58,14 @@ namespace Volo.Abp.Domain.Entities
{
_distributedEvents.Clear();
}
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
}
[Serializable]
@ -115,5 +125,13 @@ namespace Volo.Abp.Domain.Entities
{
_distributedEvents.Clear();
}
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
}
}

13
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs

@ -5,6 +5,8 @@ using Volo.Abp.Reflection;
namespace Volo.Abp.Data
{
//TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0
public static class HasExtraPropertiesExtensions
{
public static bool HasProperty(this IHasExtraProperties source, string name)
@ -12,17 +14,18 @@ namespace Volo.Abp.Data
return source.ExtraProperties.ContainsKey(name);
}
public static object GetProperty(this IHasExtraProperties source, string name)
public static object GetProperty(this IHasExtraProperties source, string name, object defaultValue = null)
{
return source.ExtraProperties?.GetOrDefault(name);
return source.ExtraProperties?.GetOrDefault(name)
?? defaultValue;
}
public static TProperty GetProperty<TProperty>(this IHasExtraProperties source, string name)
public static TProperty GetProperty<TProperty>(this IHasExtraProperties source, string name, TProperty defaultValue = default)
{
var value = source.GetProperty(name);
if (value == default)
if (value == null)
{
return default;
return defaultValue;
}
if (TypeHelper.IsPrimitiveExtended(typeof(TProperty), includeEnums: true))

2
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs

@ -2,6 +2,8 @@
namespace Volo.Abp.Data
{
//TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0
public interface IHasExtraProperties
{
Dictionary<string, object> ExtraProperties { get; }

11
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs

@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
[Serializable]
public class ExtensibleObject : IHasExtraProperties
public class ExtensibleObject : IHasExtraProperties, IValidatableObject
{
public Dictionary<string, object> ExtraProperties { get; protected set; }
@ -13,5 +14,13 @@ namespace Volo.Abp.ObjectExtending
{
ExtraProperties = new Dictionary<string, object>();
}
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
}
}

200
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs

@ -0,0 +1,200 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Data;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.ObjectExtending
{
public static class ExtensibleObjectValidator
{
[NotNull]
public static bool IsValid(
[NotNull] IHasExtraProperties extensibleObject,
[CanBeNull] ValidationContext objectValidationContext = null)
{
return GetValidationErrors(
extensibleObject,
objectValidationContext
).Any();
}
[NotNull]
public static List<ValidationResult> GetValidationErrors(
[NotNull] IHasExtraProperties extensibleObject,
[CanBeNull] ValidationContext objectValidationContext = null)
{
var validationErrors = new List<ValidationResult>();
AddValidationErrors(
extensibleObject,
validationErrors,
objectValidationContext
);
return validationErrors;
}
public static void AddValidationErrors(
[NotNull] IHasExtraProperties extensibleObject,
[NotNull] List<ValidationResult> validationErrors,
[CanBeNull] ValidationContext objectValidationContext = null)
{
Check.NotNull(extensibleObject, nameof(extensibleObject));
Check.NotNull(validationErrors, nameof(validationErrors));
if (objectValidationContext == null)
{
objectValidationContext = new ValidationContext(
extensibleObject,
null,
new Dictionary<object, object>()
);
}
var objectType = ProxyHelper.UnProxy(extensibleObject).GetType();
var objectExtensionInfo = ObjectExtensionManager.Instance
.GetOrNull(objectType);
if (objectExtensionInfo == null)
{
return;
}
AddPropertyValidationErrors(
extensibleObject,
validationErrors,
objectValidationContext,
objectExtensionInfo
);
ExecuteCustomObjectValidationActions(
extensibleObject,
validationErrors,
objectValidationContext,
objectExtensionInfo
);
}
private static void AddPropertyValidationErrors(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionInfo objectExtensionInfo)
{
var properties = objectExtensionInfo.GetProperties();
if (!properties.Any())
{
return;
}
foreach (var property in properties)
{
AddPropertyValidationErrors(extensibleObject, validationErrors, objectValidationContext, property);
}
}
private static void AddPropertyValidationErrors(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionPropertyInfo property)
{
AddPropertyValidationAttributeErrors(
extensibleObject,
validationErrors,
objectValidationContext,
property
);
ExecuteCustomPropertyValidationActions(
extensibleObject,
validationErrors,
objectValidationContext,
property
);
}
private static void AddPropertyValidationAttributeErrors(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionPropertyInfo property)
{
if (!property.ValidationAttributes.Any())
{
return;
}
var propertyValidationContext = new ValidationContext(extensibleObject, objectValidationContext, null)
{
DisplayName = property.Name,
MemberName = property.Name
};
foreach (var attribute in property.ValidationAttributes)
{
var result = attribute.GetValidationResult(
extensibleObject.GetProperty(property.Name),
propertyValidationContext
);
if (result != null)
{
validationErrors.Add(result);
}
}
}
private static void ExecuteCustomPropertyValidationActions(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionPropertyInfo property)
{
if (!property.Validators.Any())
{
return;
}
var context = new ObjectExtensionPropertyValidationContext(
property,
extensibleObject,
validationErrors,
objectValidationContext,
extensibleObject.GetProperty(property.Name)
);
foreach (var validator in property.Validators)
{
validator(context);
}
}
private static void ExecuteCustomObjectValidationActions(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionInfo objectExtensionInfo)
{
if (!objectExtensionInfo.Validators.Any())
{
return;
}
var context = new ObjectExtensionValidationContext(
objectExtensionInfo,
extensibleObject,
validationErrors,
objectValidationContext
);
foreach (var validator in objectExtensionInfo.Validators)
{
validator(context);
}
}
}
}

4
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs

@ -17,11 +17,15 @@ namespace Volo.Abp.ObjectExtending
[NotNull]
public Dictionary<object, object> Configuration { get; }
[NotNull]
public List<Action<ObjectExtensionValidationContext>> Validators { get; }
public ObjectExtensionInfo([NotNull] Type type)
{
Type = Check.AssignableTo<IHasExtraProperties>(type, nameof(type));
Properties = new Dictionary<string, ObjectExtensionPropertyInfo>();
Configuration = new Dictionary<object, object>();
Validators = new List<Action<ObjectExtensionValidationContext>>();
}
public virtual bool HasProperty(string propertyName)

9
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
namespace Volo.Abp.ObjectExtending
@ -15,6 +16,12 @@ namespace Volo.Abp.ObjectExtending
[NotNull]
public Type Type { get; }
[NotNull]
public List<ValidationAttribute> ValidationAttributes { get; }
[NotNull]
public List<Action<ObjectExtensionPropertyValidationContext>> Validators { get; }
/// <summary>
/// Indicates whether to check the other side of the object mapping
/// if it explicitly defines the property. This property is used in;
@ -42,6 +49,8 @@ namespace Volo.Abp.ObjectExtending
Name = Check.NotNull(name, nameof(name));
Configuration = new Dictionary<object, object>();
ValidationAttributes = new List<ValidationAttribute>();
Validators = new List<Action<ObjectExtensionPropertyValidationContext>>();
}
}
}

61
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionPropertyValidationContext
{
/// <summary>
/// Related property extension information.
/// </summary>
[NotNull]
public ObjectExtensionPropertyInfo ExtensionPropertyInfo { get; }
/// <summary>
/// Reference to the validating object.
/// </summary>
[NotNull]
public IHasExtraProperties ValidatingObject { get; }
/// <summary>
/// Add validation errors to this list.
/// </summary>
[NotNull]
public List<ValidationResult> ValidationErrors { get; }
/// <summary>
/// Validation context comes from the <see cref="IValidatableObject.Validate"/> method.
/// </summary>
[NotNull]
public ValidationContext ValidationContext { get; }
/// <summary>
/// The value of the validating property of the <see cref="ValidatingObject"/>.
/// </summary>
[CanBeNull]
public object Value { get; }
/// <summary>
/// Can be used to resolve services from the dependency injection container.
/// </summary>
[CanBeNull]
public IServiceProvider ServiceProvider => ValidationContext;
public ObjectExtensionPropertyValidationContext(
[NotNull] ObjectExtensionPropertyInfo objectExtensionPropertyInfo,
[NotNull] IHasExtraProperties validatingObject,
[NotNull] List<ValidationResult> validationErrors,
[NotNull] ValidationContext validationContext,
[CanBeNull] object value)
{
ExtensionPropertyInfo = Check.NotNull(objectExtensionPropertyInfo, nameof(objectExtensionPropertyInfo));
ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject));
ValidationErrors = Check.NotNull(validationErrors, nameof(validationErrors));
ValidationContext = Check.NotNull(validationContext, nameof(validationContext));
Value = value;
}
}
}

53
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionValidationContext
{
/// <summary>
/// Related object extension information.
/// </summary>
[NotNull]
public ObjectExtensionInfo ObjectExtensionInfo { get; }
/// <summary>
/// Reference to the validating object.
/// </summary>
[NotNull]
public IHasExtraProperties ValidatingObject { get; }
/// <summary>
/// Add validation errors to this list.
/// </summary>
[NotNull]
public List<ValidationResult> ValidationErrors { get; }
/// <summary>
/// Validation context comes from the <see cref="IValidatableObject.Validate"/> method.
/// </summary>
[NotNull]
public ValidationContext ValidationContext { get; }
/// <summary>
/// Can be used to resolve services from the dependency injection container.
/// </summary>
[CanBeNull]
public IServiceProvider ServiceProvider => ValidationContext;
public ObjectExtensionValidationContext(
[NotNull] ObjectExtensionInfo objectExtensionInfo,
[NotNull] IHasExtraProperties validatingObject,
[NotNull] List<ValidationResult> validationErrors,
[NotNull] ValidationContext validationContext)
{
ObjectExtensionInfo = Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo));
ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject));
ValidationErrors = Check.NotNull(validationErrors, nameof(validationErrors));
ValidationContext = Check.NotNull(validationContext, nameof(validationContext));
}
}
}

1
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs

@ -18,6 +18,7 @@ namespace Volo.Abp.Validation
context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded);
AutoAddObjectValidationContributors(context.Services);
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>

172
framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs

@ -0,0 +1,172 @@
using System.ComponentModel.DataAnnotations;
using Shouldly;
using Volo.Abp.Data;
using Volo.Abp.Threading;
using Xunit;
namespace Volo.Abp.ObjectExtending
{
public class ExtensibleObjectValidator_Tests
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
static ExtensibleObjectValidator_Tests()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.AddOrUpdate<ExtensiblePersonObject>(options =>
{
options.AddOrUpdateProperty<string>("Name", propertyInfo =>
{
propertyInfo.ValidationAttributes.Add(new RequiredAttribute());
propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(64) { MinimumLength = 2 });
});
options.AddOrUpdateProperty<string>("Address", propertyInfo =>
{
propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(255));
});
options.AddOrUpdateProperty<byte>("Age", propertyInfo =>
{
propertyInfo.ValidationAttributes.Add(new RequiredAttribute());
propertyInfo.ValidationAttributes.Add(new RangeAttribute(18, 99));
});
options.AddOrUpdateProperty<bool>("IsMarried", propertyInfo =>
{
});
options.AddOrUpdateProperty<string>("Password", propertyInfo =>
{
});
options.AddOrUpdateProperty<string>("PasswordRepeat", propertyInfo =>
{
propertyInfo.Validators.Add(context =>
{
if (context.ValidatingObject.HasProperty("Password"))
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.Value as string)
{
context.ValidationErrors.Add(
new ValidationResult(
"If you specify a password, then please correctly repeat it!",
new[] {"Password", "PasswordRepeat"}
)
);
}
}
});
});
options.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Name") == "BadValue")
{
context.ValidationErrors.Add(
new ValidationResult(
"Name can not be 'BadValue', sorry :(",
new[] { "Name" }
)
);
}
});
});
});
}
[Fact]
public void Should_Validate_If_The_Properties_Are_Valid()
{
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Name", "John"},
{"Age", "42"},
}
}
).Count.ShouldBe(0); //All Valid
}
[Fact]
public void Should_Not_Validate_If_The_Properties_Are_Not_Valid()
{
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject()
).Count.ShouldBe(2); // Name & Age
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Address", new string('x', 256) }
}
}
).Count.ShouldBe(3); // Name, Age & Address
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Age", "42" }
}
}
).Count.ShouldBe(1); // Name
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Address", new string('x', 256) },
{"Age", "100" }
}
}
).Count.ShouldBe(3); // Name, Age & Address
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Name", "John"},
{"Age", "42"},
{"Password", "123"},
{"PasswordRepeat", "1256"}
}
}
).Count.ShouldBe(1); // PasswordRepeat != Password
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Name", "BadValue"},
{"Age", "42"},
}
}
).Count.ShouldBe(1); //Name is 'BadValue'!
}
private class ExtensiblePersonObject : ExtensibleObject
{
}
}
}

4
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs

@ -16,18 +16,22 @@ namespace Volo.Abp.TestApp.Testing
city.HasProperty("UnknownProperty").ShouldBeFalse();
city.GetProperty("UnknownProperty").ShouldBeNull();
city.GetProperty<int>("UnknownProperty").ShouldBe(0);
city.GetProperty<int>("UnknownProperty", 42).ShouldBe(42);
city.SetProperty("IsHot", true);
city.HasProperty("IsHot").ShouldBeTrue();
city.GetProperty<bool>("IsHot").ShouldBeTrue();
city.GetProperty<bool>("IsHot").ShouldBeTrue();
city.SetProperty("IsHot", false);
city.HasProperty("IsHot").ShouldBeTrue();
city.GetProperty<bool>("IsHot").ShouldBeFalse();
city.GetProperty<bool>("IsHot", true).ShouldBeFalse();
city.RemoveProperty("IsHot");
city.HasProperty("IsHot").ShouldBeFalse();
city.GetProperty<bool>("IsHot").ShouldBeFalse();
city.GetProperty<bool>("IsHot", true).ShouldBeTrue();
}
}
}
Loading…
Cancel
Save