MVC - 控制器与服务层通信

6
在我的ASP.net mvc应用程序中,我使用服务层和存储库来保持控制器的简洁。一个典型的只读详细信息视图如下所示:
public ActionResult Details(int id)
{
    var project = _projectService.GetById(id);

    return View(Mapper.Map<Project, ProjectDetails>(project));
}

服务层:

public class ProjectService : IProjectService
{
    public Project GetById(int id)
    {
        var project = _projectRepository.GetProject(id);

        // do some stuff

        return project;
    }
}

public class ProjectRepository : IProjectRepository
{
    public Project GetProject(int id)
    {
        return context.Projects.Find(id);
    }
}

由于 AutoMapper 可以轻松地将事物展平,因此从服务层转移到视图模型非常容易。然而,将视图模型直接传递到我的服务层是我难以找到好的解决方案的地方。

在像创建操作这样的情况下,有什么好的方法吗?

[HttpPost]
public ActionResult Create(CreateProjectViewModel model)
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }

    // TODO

    return RedirectToAction("Index");
}

我相信服务层不应该知道任何关于视图模型的信息,但我也认为AutoMapper在这种情况下并不适用,因为它不能很好地将一个平面模型转换成一个复杂的对象。

我的控制器应该如何与服务层通信?我希望保持控制器中的代码尽可能简洁。


1
你知道吗,我也一直觉得AutoMapper在这方面非常繁琐,如果Resharper之类的工具能够自动创建复杂对象的映射方式,那会很好。这可能成为一个不错的插件。 - Erik Funkenbusch
关于使用AutoMapper进行非扁平化操作:您是否查看了https://dev59.com/RXA75IYBdhLWcg3wuLjD? - rsenna
3个回答

6
你可以定义一个双向映射,然后反过来做:
[HttpPost]
public ActionResult Create(CreateProjectViewModel model)
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }

    Project project = Mapper.Map<CreateProjectViewModel, Project>(model);
    // pass the project entity to your service layer
    _projectService.Create(project);

    return RedirectToAction("Index");
}

如果您正在更新实体,则可能首先需要从服务中获取要更新的现有实体:

[HttpPost]
public ActionResult Update(CreateProjectViewModel model)
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }

    Project project = _projectService.GetById(model.Id);
    Mapper.Map<CreateProjectViewModel, Project>(model, project);

    // pass the project entity to your service layer
    _projectService.Update(project);

    return RedirectToAction("Index");
}

1
有时候似乎这并不是很有效,因为AutoMapper不能轻易地从扁平模型转换为复杂模型。 - Dismissile
1
@Dismissile,嗯,一切都取决于您如何定义映射。使用AutoMapper扁平化确实更容易,但您也可以反过来从视图模型重构原始领域模型。 - Darin Dimitrov
你通常在项目中采用这种方法吗? - Dismissile
@Dismissile,这取决于项目的具体要求和上下文。但总的来说,这是我喜欢的方法。 - Darin Dimitrov
最后一个问题。在您使用automapper从视图模型转换为域模型的项目中,您的域模型是否与实体框架模型相同?视图模型<-->实体(服务层)<-->实体(存储库)。基本上,存储库和服务层都处理相同的对象? - Dismissile
@Dismissile,我在我的项目中不使用Entity Framework,因此无法针对自己发表意见。但是如果我必须使用它,我可能会将EF自动生成的类作为服务层中的领域模型进行重用。但是在更复杂的项目中,您已经拥有一些现有的领域模型,在这种情况下,EF类应视为ORM实现特定的,可能仅在数据访问层使用。个人而言,我不支持我的领域模型包含数据访问特定的类,如DbSet<T>等。 - Darin Dimitrov

1
到目前为止我看到的唯一方法是手动创建一堆模型转换类,例如:
public interface ITransformer<out To, in From>
    where To : class
{
    To Transform(From instance);
}

public class SomeDataToSomeViewModelTransformer : ITransformer<SomeViewModel, SomeDataModel>
{
    public SomeViewModel Transform(SomeDataModel instance)
    {
        return new SomeViewModel
            {
                InvitationId = instance.Id,
                Email = instance.EmailAddress,
                GroupId = instance.Group.Id
            };
    }
}

另外还有一个Transformer实现,可以反向转换(ViewModel -> DataModel)。并且让Controller知道调用正确的转换器。
我支持你的问题,因为我也希望能够找到一种漂亮干净的方法来做到这一点,而不必手动编写大量的代码来映射模型。

我认为这很容易实现。使用依赖注入框架连接变压器将非常简单。 - Dismissile

0

如果您的服务层仅专注于支持MVC应用程序而没有其他客户端,您可以考虑将通过服务层传递和传出的对象作为视图模型的一部分使用。这将消除自动映射入站调用的需要,因为您将发送控制器所需的实际对象。

您还可以考虑让服务返回域对象,这意味着应该在服务方法而不是控制器操作中调用自动映射。


服务层也被WCF应用程序使用,因此它无法真正操作视图模型。 - Dismissile
好的。我的方法不适合你。 - Nick Ryan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接