在ASP.NET MVC中扩展具有功能的实体模型是否合法

时间:2012-03-01 09:18:31

标签: asp.net-mvc entity-framework

首先,这是我的情况。我正在使用带有Entity Framework 4.1的ASP.NET MVC 3编写Intranet应用程序。我的应用程序是使用“工作单元”和“存储库”设计模式开发的。

在我看来,我的应用程序有一个工作单元可以提供对所有存储库的集中访问,进一步提供对实体的访问。

假设我有一个名为“ProductApprovalDocument”的实体,其属性“id”,“creationDate”和“approvalDecission”存储在数据库中。现在,我希望用户能够访问该实体很快描述的文档的PDF文件。因为文件使用URL格式“[fileServerDirectoryPath] / [ProductApprovalDocument.id] .pdf”存储在文件服务器的中央目录中,所以我不想在数据库上为该文件路径保存额外的属性。我想做的是给实体一个名为“filepath”的额外属性,该属性自动构造具有给定信息的路径并返回它。

现在问题:

我使用名为FileService的接口来抽象应用程序其余部分的文件访问。现在在我的情况下,我将不得不从实体模型中访问UnitOfWork对象,以检索当前的FileService实现并获取预配置的文件路径。我认为这是完全错误的方式,因为对我来说,实体模型应该只用作数据容器而不是更多或更少。

现在问题:

我该如何处理这种情况。我不想总是通过控制器设置filepath属性,因为它或多或少是静态的,因此可以通过模型以某种方式自动完成。

编辑(最终解决方案):

感谢Andre Loker的回答,我对我的问题有了另一种观点。

  • 我想达到的核心目标是什么?
    • 我希望用户能够访问存储在文件服务器上的文件。
  • 我是否必须为每个显示的实体提供总文件路径?
    • 没有!想想MVC的原理!用户操作及时由控制器处理。您不必提供信息,直到它真正被使用。

因此,解决方案只是像往常一样呈现所有数据,而不是显示文件的静态html链接,您必须在Controller中包含一个ActionLink,它会动态计算文件路径并自动将用户重定向到文件

在视图中执行此操作:

@Html.ActionLink(Model.ID.ToString(), "ShowProductApprovalDocumentFile", "ProductApprovalDocument", new { ProductApprovalDocumentID = Model.ID }, null)

而不是:

<a href="@Model.FilePath">@Model.ID</a>

并向控制器添加相应的Action:

public ActionResult ShowProductApprovalDocumentFile(int ProductApprovalDocumentID )
{
   return Redirect(_unitOfWork.FileService.GetFilePathForProductApprovalDocument(ProductApprovalDocumentID));
}

感谢那些花时间给我答案的人,特别感谢安德烈带领我获得满意的答案! :)

2 个答案:

答案 0 :(得分:1)

如果我正确理解了该属性,有几种选择:

1)使FilePath属性使用服务定位器来查找FileService:

public string FilePath {
    get {
        FileService fileService = DependencyResolver.Current.GetService<FileService>();
        return fileService.GetFilePathForDocument(this);
    }
}

虽然我不是静态服务定位器的狂热粉丝,因为它们使测试更加困难,但这可能是一个可行的选择。为了使其更易于测试,您可以使文件服务定位器可注入:

private static readonly Func<FileService> defaultFileServiceLocator = ()=>DependencyResolver.Current.GetService<FileService>():
private Func<FileService> fileServiceLocator = defaultFileServiceLocator;

public Func<FileService> FileServiceLocator { 
    get { return fileServiceLocator; }
    set { fileServiceLocator = value ?? defaultFileServiceLocator; }
}

然后在FilePath中使用它

public string FilePath {
    get {
        FileService fileService = fileServiceLocator();
        return fileService.GetFilePathForDocument(this);
    }
}

这样您就可以在测试期间注入自己的文件服务定位器。

2)检索文件路径时明确要求FileService。而不是你有的FilePath属性:

public string GetFilePath(FileService service){
    service.GetFilePathForDocument(this);
}

这个问题当然是现在GetFilePath的调用者需要一个FileService。这对控制器来说不是什么大问题,因为如果使用IoC,可以将FileService注入控制器构造函数。这种方法更简洁,因为它不依赖于服务定位器,但正如您所看到的那样,对于调用者来说稍微不方便。

3)将FileService注入文档类本身。

在构造ProductApprovalDocument时,您将自己注入文件服务,而不是使用文件服务定位器。使用此方法,您可以再次使用简单的FilePath属性。主要的问题是,这通常不能很好地与ORM一起使用,因为它们通常使用默认构造函数构造对象,并且您必须以某种方式挂钩到对象构造过程以注入依赖项。此外,我不是域对象注入服务的忠实粉丝。

4)从实体外部设置FilePath。如你所说,这应该在某种程度上自动完成,因为你不想每次都手动完成。这将需要一些层,所有实体都需要通过该层来设置FilePath属性。

5)根本不要将FilePath作为ProductApprovalDocument的属性。这也是一个合理的选择。 ProductApprovalDocument对其FilePath一无所知,为什么它应该是属性?它是计算值的FileService。您仍然可以拥有ProductApprovalDocument的独特视图模型版本, 具有FilePath属性。您在创建视图模型时设置了属性:

var model = new ProductApprovalDocumentViewModel();
mapper.Map(realDocument, model); // map common properties with AutoMapper or so
model.FilePath = fileService.GetFilePathForDocument(realDocument);

但是,如果ProductApprovalDocument需要对其FilePath做一些事情(为什么会这样?),这种方法不再适用。

就个人而言,如果适用,我会按照优先顺序使用解决方案5,2或1。

答案 1 :(得分:0)

虽然我会犹豫是否依赖于能够计算文件路径而我宁愿将其存储为实体的一部分(如果由于某种原因需要更改),在你的情况下,如果我坚持我想按照你所说的方式去做,我想我会扩展FileService / ViewModel,使其具有Filepath属性,该属性是以你所说的方式派生的。

e.g。如果我想创建下载链接,请在ViewModel

中执行此操作
public string FilePath
{
   get
   {
      return String.Format(@"thehardcodedbit{0}.pdf",ID);
   }
}

编辑:如果你有一个由EF4.x生成的实体,那么它将作为一个部分类生成,所以你总是可以像这样扩展它(我做过这样的事情,它工作正常):

假设生成的实体如下所示:

Namespace Da_Wolf.Model.Entities.File
{
   public partial class UploadedFile
   {....}
}

然后你可以创建一个这样的局部类:

Namespace Da_Wolf.Model.Entities.File
{
   public partial class UploadedFile
   {
     public string FilePath
     {
        get
        {
           return String.Format(@"thehardcodedbit{0}.pdf",ID);
        }
     }  
   }
}

现在,您可以随处获得所需的属性,而无需向ViewModel添加任何内容。