1

让我们有以下实体的系统:

public class Doctor
{
    public int ID { get; set; }
    public int DepartmentID { get; set; }
    public string Name { get; set; }
    public ICollection<Recipe> Recipes { get; set; }
}

public class Patient
{
    public int ID { get; set; }
    public string Name { get; set; }
    public ICollection<Recipe> Recipes { get; set; }
}

public class Recipe
{
    public int ID { get; set; }
    public int DoctorID { get; set; }
    public int PatientID { get; set; }
    public Doctor Doctor { get; set; }
    public Patient Patient { get; set; }
    public ICollection<RecipeDetails> Details { get; set; }
}

public class RecipeDetails
{
    public int ID { get; set; }
    public Guid SomeGuid { get; set; }
    public double SomeValue { get; set; }
}

我们也有要求:

  • 医生应该能够编辑他的食谱
  • 医生应该只能看到他部门内医生的食谱
  • 医生应该能够通过可用的食谱进行搜索
  • 医生应该能够根据可用的食谱详细信息生成报告

现在我已经实施了以下安全检查:

public void ValidateAccess(Doctor doctor, Recipe aRecipe, EntityAction action)
{
    if (action == EntityAction.Modify && doctor.ID == aRecipe.Doctor.ID)
        return;
    if (action == EntityAction.Read && doctor.DepartmentID == aRecipe.Doctor.DepartmentID)
        return
    throw new SecurityException();
}

当我有收据实体时,这非常适合简单的方法,我可以通过在我的逻辑方法开始时调用此方法来轻松验证访问。

但是现在我遇到了问题,当我没有确切的实体但对它们有一些统计数据时,这个解决方案将不适用于搜索和报告。

让我们想象一下,我想为名称为“aName”的患者生成报告,这些患者的收据带有“someGuid”组件,我将有一些带有 2 个标准的查询:

var res = RecipeRepository.Query(r => aName.Contains(r.Patient.Name)).SelectMany(r => r.Details).Where(d => d.SomeGuid == someGuid).Sum(d => d.SomeValue);

此查询不正确,它将显示所有配方的统计信息,包括那些应该隐藏的配方。为了解决这个问题,我们应该将我们的访问条件添加到我们的查询中:

currentDoctor.DepartmentID == r.Doctor.DepartmentID

所以现在我有疑问:

var res = RecipeRepository.Query(r => aName.Contains(r.Patient.Name) && currentDoctor.DepartmentID == r.Doctor.DepartmentID).SelectMany(r => r.Details).Where(d => d.SomeGuid == someGuid).Sum(d => d.SomeValue);

问题是我应该将此部分添加到系统中对收据进行任何计算的每个查询中。

更新(2012-11-12):

第一个例子非常简单,可以像 StuartLC 在他的帖子中提到的那样解决。但是我们的系统中有更复杂的报告。例如 - 显示所有在他们的食谱中包含 someGuid 组件的患者。现在我们的查询从另一个存储库开始,因此我们不能应用来自 RecipeRepository 的私有或受保护方法。这是示例查询:

var res = PatientRepository.Query(p => p.Name.Contains(aName) && p.Recipes.Any(r => r.Details.Any(d => d.SomeGuid == someGuid)));

在这种情况下,我们仍然需要将过滤器直接添加到查询中:

var res = PatientRepository.Query(p => p.Name.Contains(aName) && p.Recipes.Any(r => currentDoctor.DepartmentID == r.Doctor.DepartmentID && r.Details.Any(d => d.SomeGuid == someGuid)));

结束更新。

可以应用什么模式或实践来简化此解决方案并防止将表达式复制粘贴到每个查询?我会很感激你的回答和建议。

4

1 回答 1

1

如果您的存储库模式Query()方法返回一个 non-materialized IQueryable<T>,那么您可以将数据访问限制的问题重构为帮助方法,每个“可限制”实体一个,例如:

private IQueryable<Recipe> ApplyAccessFilters(IQueryable<Recipe> query, User user)
{
    IQueryable<Recipe> filteredQuery = query;

    // Custom method to determine if user is restricted to just his / her recipes
    if (!CheckUserPermission(currentUser, Access.MaySeeAllRecipies) ))
    {
        filteredQuery = filteredQuery
                              .Where(r => r.DepartmentId = currentUser.DepartmentId)
    } // Else no restriction, e.g. Admin Users can access all recipes

    // Other access related filters here

    return filteredQuery;
}

然后,每个需要访问限制的 MVC 控制器操作都可以使用此方法来构建生成的过滤器表达式,例如:

var recipes =  RecipeRepository.Query(r => r.SomeFields == someFilters); // NB, do NOT materialize the lambda
var recipesForDoctor =  ApplyAccessFilters(recipes, currentUser) // Access Filter
...
return View(recipesForDoctor); // [AsEnumerable()] - Consider materializing here

您可以以相同的方式处理其他问题,例如分页。

更棒的是,你可以让这个访问过滤器fluent,在这种情况下过滤器很容易看:

return View(RecipeRepository
            .Query(r => r.SomeFields == someFilters)
            .ApplyAccessFilters(currentUser)
            .Paginate(pagingInfo)
            .AsEnumerable());
于 2012-11-08T09:38:03.230 回答