diff --git a/README.md b/README.md
index a0184a6..861ba7b 100644
--- a/README.md
+++ b/README.md
@@ -163,6 +163,10 @@ As you can see in the example above BlazorDB will detect associations added to t
**Note:** At this time removing/deleting is not done automatically and needs to be done manually. A future update of BlazorDB will handle deletions properly.
+## Validations
+
+You can annotate your model's propeties with `[Required]` and `[MaxLength(int)]` to enforce required and max length on properties.
+
## Example
A Todo sample built with BlazorDB is included in the sample project:
diff --git a/src/BlazorDB/BlazorDB.csproj b/src/BlazorDB/BlazorDB.csproj
index 2672604..990e5c4 100644
--- a/src/BlazorDB/BlazorDB.csproj
+++ b/src/BlazorDB/BlazorDB.csproj
@@ -7,7 +7,7 @@
false7.3BlazorDB
- 0.1.2
+ 0.1.3Chanan Braunstein
Blazor localStorage DatabaseIn memory, persisted to localstorage, database for .net Blazor browser framework
diff --git a/src/BlazorDB/BlazorDBUpdateException.cs b/src/BlazorDB/BlazorDBUpdateException.cs
new file mode 100644
index 0000000..70e97a4
--- /dev/null
+++ b/src/BlazorDB/BlazorDBUpdateException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace BlazorDB
+{
+ public class BlazorDBUpdateException : Exception
+ {
+ public BlazorDBUpdateException(string error) : base(error)
+ {
+ }
+ }
+}
diff --git a/src/BlazorDB/DataAnnotations/MaxLength.cs b/src/BlazorDB/DataAnnotations/MaxLength.cs
new file mode 100644
index 0000000..a02bd57
--- /dev/null
+++ b/src/BlazorDB/DataAnnotations/MaxLength.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace BlazorDB.DataAnnotations
+{
+ [AttributeUsage(AttributeTargets.Property)]
+ public class MaxLength : Attribute
+ {
+ public MaxLength(int length)
+ {
+ this.length = length;
+ }
+
+ internal int length;
+ }
+}
\ No newline at end of file
diff --git a/src/BlazorDB/DataAnnotations/Required.cs b/src/BlazorDB/DataAnnotations/Required.cs
new file mode 100644
index 0000000..d6984ee
--- /dev/null
+++ b/src/BlazorDB/DataAnnotations/Required.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace BlazorDB.DataAnnotations
+{
+ [AttributeUsage(AttributeTargets.Property)]
+ public class Required : Attribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/BlazorDB/Storage/StorageManagerSave.cs b/src/BlazorDB/Storage/StorageManagerSave.cs
index 1ef18fc..25247d8 100644
--- a/src/BlazorDB/Storage/StorageManagerSave.cs
+++ b/src/BlazorDB/Storage/StorageManagerSave.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using BlazorDB.DataAnnotations;
using Microsoft.AspNetCore.Blazor;
namespace BlazorDB.Storage
@@ -15,12 +16,73 @@ public int SaveContextToLocalStorage(StorageContext context)
var contextType = context.GetType();
Logger.ContextSaved(contextType);
var storageSets = StorageManagerUtil.GetStorageSets(contextType);
- var metadataMap = LoadMetadataList(context, storageSets, contextType);
- total = SaveStorageSets(context, total, contextType, storageSets, metadataMap);
- Logger.EndGroup();
+ var error = ValidateModels(context, storageSets);
+ if (error == null)
+ {
+ var metadataMap = LoadMetadataList(context, storageSets, contextType);
+ total = SaveStorageSets(context, total, contextType, storageSets, metadataMap);
+ Logger.EndGroup();
+ }
+ else
+ {
+ BlazorLogger.Logger.Error("SaveChanges() terminated due to validation error");
+ Logger.EndGroup();
+ throw new BlazorDBUpdateException(error);
+ }
+
return total;
}
+ private string ValidateModels(StorageContext context, IEnumerable storageSets)
+ {
+ string error = null;
+ foreach (var storeageSetProp in storageSets)
+ {
+ var storeageSet = storeageSetProp.GetValue(context);
+ var listProp = storeageSet.GetType().GetProperty(StorageManagerUtil.List, StorageManagerUtil.Flags);
+ var list = listProp.GetValue(storeageSet);
+ var method = list.GetType().GetMethod(StorageManagerUtil.GetEnumerator);
+ var enumerator = (IEnumerator)method.Invoke(list, new object[] { });
+ while (enumerator.MoveNext())
+ {
+ var model = enumerator.Current;
+ foreach (var prop in model.GetType().GetProperties())
+ {
+ if (Attribute.IsDefined(prop, typeof(Required)))
+ {
+ var value = prop.GetValue(model);
+ if (value == null)
+ {
+ error =
+ $"{model.GetType().FullName}.{prop.Name} is a required field. SaveChanges() has been terminated.";
+ break;
+ }
+ }
+
+ if (Attribute.IsDefined(prop, typeof(MaxLength)))
+ {
+ var maxLength = (MaxLength) Attribute.GetCustomAttribute(prop, typeof(MaxLength));
+ var value = prop.GetValue(model);
+ if (value != null)
+ {
+ var str = value.ToString();
+ if (str.Length > maxLength.length)
+ {
+ error =
+ $"{model.GetType().FullName}.{prop.Name} length is longer than {maxLength.length}. SaveChanges() has been terminated.";
+ break;
+ }
+ }
+ }
+ }
+
+ if (error != null) break;
+ }
+ }
+
+ return error;
+ }
+
private static IReadOnlyDictionary LoadMetadataList(StorageContext context,
IEnumerable storageSets, Type contextType)
{
diff --git a/src/Sample/Models/Person.cs b/src/Sample/Models/Person.cs
index c5b0e5b..5cb8b87 100644
--- a/src/Sample/Models/Person.cs
+++ b/src/Sample/Models/Person.cs
@@ -1,11 +1,14 @@
using System.Collections.Generic;
+using BlazorDB.DataAnnotations;
namespace Sample.Models
{
public class Person
{
public int Id { get; set; }
+ [Required]
public string FirstName { get; set; }
+ [MaxLength(20)]
public string LastName { get; set; }
public Address HomeAddress { get; set; }
public List OtherAddresses { get; set; }
diff --git a/src/Sample/Pages/Index.cshtml b/src/Sample/Pages/Index.cshtml
index 589177f..1df64a5 100644
--- a/src/Sample/Pages/Index.cshtml
+++ b/src/Sample/Pages/Index.cshtml
@@ -12,6 +12,11 @@
+
Validations
+
+
+
+
@if (@Person != null)
{
@@ -54,4 +59,18 @@
{
Context.SaveChanges();
}
+
+ void OnRequired()
+ {
+ var person = new Person {LastName = "Firstname required!"};
+ Context.People.Add(person);
+ Context.SaveChanges();
+ }
+
+ void OnMaxLength()
+ {
+ var person = new Person { FirstName = "Lastname is long", LastName = "This is a very long last name and should throw!" };
+ Context.People.Add(person);
+ Context.SaveChanges();
+ }
}