Представим себе некую учетную систему компании, производящей сферических коней.
Естественно, в модели данных этой учетной системы есть модель данных SphericalHorse:
public class SphericalHorse
{
[Key]
public Guid SerialNumber { get; set; }
public float Radius { get; set; }
public string Color { get; set; }
}
Руководство компании решило, что серийный номер коня будет присваиваться в момент добавления информации о новом изделии в БД и реализация данного функционала будет отдана системе хранения. Далее это же руководство решило, что даст возможность пользователям УС с ролью ServiceManager менять серийный номер, только им и больше никому. В учетной системе используется система безопасности .Net на основе ролей.
В качестве инфраструктуры, связывающей модель данных и систему хранения может быть использована любая ORM, в частности для тестирования используется Entity Framework.
Отразим эти бизнес-требования (а они же и правила безопасности доступа к свойству SerialNumber) в тестах класса SphericalHorse.
[TestMethod]
public void SphericalHorse_Constructor_Autogenerate_SerialNumber()
{
SphericalHorse horse = new SphericalHorse() { };
using(SecureDALCtxt ctx = new SecureDALCtxt())
{
ctx.Horses.Add(horse);
ctx.SaveChanges();
}
Assert.AreNotEqual(Guid.Empty, horse.SerialNumber);
}
[TestMethod]
public void SphericalHorse_SetSerialNumber_NotServiceManager()
{
SphericalHorse horse;
using(SecureDALCtxt ctx = new SecureDALCtxt())
{
horse = (from h in ctx.Horses select h).First();
}
Assert.IsNotNull(horse);
try
{
Guid newSN = new Guid("ADD1098D-2EF4-4B64-8BC7-6BCAB08A9331");
horse.SerialNumber = newSN;
}
catch (SecurityException)
{
return;
}
Assert.Fail("Ожидалось исключение безопасности");
}
[TestMethod]
public void SphericalHorse_SetSerialNumber_ServiceManager()
{
SphericalHorse horse;
using (SecureDALCtxt ctx = new SecureDALCtxt())
{
horse = (from h in ctx.Horses select h).First();
}
Assert.IsNotNull(horse);
Thread.CurrentPrincipal = new GenericPrincipal(
new GenericIdentity("Manager"),
new string[] { "ServiceManager" }
);
Guid newSN = new Guid("A5CA5613-EAFB-41C1-8192-0FA5C79809D9");
horse.SerialNumber = newSN;
Assert.AreEqual(newSN, horse.SerialNumber);
}
Реализуем бизнес правила в модели
public class SphericalHorse
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid SerialNumber {
get;
[PrincipalPermission(SecurityAction.Demand, Role = "ServiceManager")]
set;
}
public float Radius { get; set; }
public string Color { get; set; }
}
В результате, ни один из тестов не прошел. Все падают в момент, когда Entity Framework пытается установить значение свойства SerialNumber.
Изменим немного интерфейс коня
public class SphericalHorse
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid SerialNumber {
get;
private set;
}
[PrincipalPermission(SecurityAction.Demand, Role = "ServiceManager")]
public void SetSerialNumber(Guid value)
{
this.SerialNumber = value;
}
public float Radius { get; set; }
public string Color { get; set; }
}
Ну и соответственно тесты...
В итоге, все вроде работает в соответствии с бизнес-требованиями.
В тоже время есть два момента:
- Мы потеряли удобство использования методов-асессоров
- Если вдруг руководство компании в будущем решит изменить правила доступа к другим свойствам коня, то придется переписывать приложения, которые используют модель коня
Подскажите пожалуйста, как более правильно реализовывать такие задачи?