ASP.NET MVC设计模式最佳实践与服务

3

我有一个ASP.NET MVC 3应用程序。

我有一个ModelViewModelViewController

我使用Ninject作为IoC。

我的Controller使用ViewModel将数据传递给View

我已经开始使用Service(具体和接口类型)从ViewModel中获取信息,并针对数据库进行查询以操纵它。

我能否使用相同的Service来设置ViewModel?还是这违反了设计模式的原则?

也就是说,我能否在Service层中抽象出设置ViewModel的方法?

场景

情况是:我的Model有很多对其他Models的引用,因此当我在控制器中设置ViewModel时,它显得太冗长,我感觉Controller正在做太多事情。因此,我希望能够像这样做:

var vm = _serviceProvider.SetupViewModel(Guid model1Id, Guid model2Id, /*etc..*/)

ServiceProvider中的SetupViewModel函数将如下所示:

public MyModelViewModel SetupViewModel(Guid model1Id, Guid model2Id, /*etc...*/)
{
    var vm = new MyModelViewModel();
    var model1 = _repository.Model1s.FirstOrDefault(x => x.Id.Equals(model1Id));
    var model2 = _repository.Model2s.FirstOrDefault(x => x.Id.Equals(model2Id));
// etc....

    vm.Model1 = model1;
    vm.Model2 = model2;

    return vm;
}

通过这样做,我也可以添加一些null条件,而不必担心使我的Controller变得非常大!对于创建/编辑操作,我使用1个ViewModel。我不会在其他地方重用ViewModel

能否提供一些关于你特定架构的更多信息呢?也许可以提供一个简短的代码示例来解释你正在尝试做什么? - Robert Harvey
我稍微编辑了我的问题,但是我没有在其中放置代码。 - Callum Linington
1
这听起来像是在重复使用ViewModels,这是不好的。 - Phill
@RobertHarvey ViewModel 中没有任何操作发生... 它只保留 View 需要呈现 ListBox 等的信息... - Callum Linington
1
没有所谓的“正确”。只有最符合客户要求的东西。:) 对于一个项目来说至关重要的东西,对另一个项目来说可能完全不必要。 - Robert Harvey
显示剩余5条评论
2个回答

5
我会让服务层返回一个领域模型,并在控制器中将其映射到视图模型。
这样,您可以使用具有多个视图模型的服务方法,例如桌面和移动视图。
您可以让 AutoMapper为您完成繁重的工作,也可以手动执行,通过在视图模型中创建一个构造函数,该构造函数接受领域模型。
领域模型:
public class Customer
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string Telephone { get; set; }

    public string Email { get; set; }

    public virtual ICollection<Order> Orders { get; set; }
}

视图模型:

public class CustomerWithOrdersModel
{
    public CustomerWithOrdersModel(Customer customer)
    {
        Id = customer.Id;
        FullName = string.Format("{0}, {1}", customer.LastName, customer.FirstName);
        Orders = customer.Orders.ToList();
    }

    public int Id { get; set; }

    public string FullName { get; set; }

    public IEnumerable<Order> Orders { get; set; }
}

编辑:AutoMapper示例:

包含从CustomerCustomerWithOrdersModel的映射的AutoMapper配置文件:

public class ViewModelProfile : Profile
{
    public override string ProfileName
    {
        get { return "ViewModel"; }
    }

    protected override void Configure()
    {
        CreateMap<Customer, CustomerWithOrdersModel>()
            .ForMember(dest => dest.FullName, opt => opt.MapFrom(src => string.Format("{0}, {1}", src.LastName, src.FirstName)))
            .ForMember(dest => dest.Orders, opt => opt.MapFrom(src => src.Orders.ToList()));
    }
}

Id按照约定进行映射。

ViewModelProfile的扩展方法:

public static class ViewModelProfileExtensions
{
    public static CustomerWithOrdersModel ToModel(this Customer customer)
    {
        return Mapper.Map<CustomerWithOrdersModel>(customer);
    }

    public static Customer ToEntity(this CustomerWithOrdersModel customerWithOrdersModel)
    {
        return Mapper.Map<Customer>(customerWithOrdersModel);
    }
}

控制器动作:

public ActionResult Details(int customerId)
{
    Customer customer = _customerService.GetById(customerId);
    CustomerWithOrdersModel customerWithOrders = customer.ToModel();
    return View(customerWithOrders);
}

如果您创建了从CustomerWithOrdersModelCustomer的映射,您可以使用customerWithOrdersModel.ToEntity()将其映射回领域模型。
就这样!您可以从ViewModel中删除具有Customer领域模型的构造函数。

你能否给出一个使用AutoMapper的例子,以证明它为什么更好用?我的当前方法创建和使用ViewModels比你提出的方法要好得多,但我只是想摆脱在复杂的Business Model中拥有冗长的ViewModelsControllers - Callum Linington
那真是太酷了,我得开始一个全新的项目来发现使用AutoMapper的所有复杂性。 - Callum Linington
@No1_Melman 你一定要试试。你还可以创建扩展方法,比如 ToEntityToModel,以将 AutoMapper 抽象出来,这也会导致更少的代码。 - Henk Mollema
那么现在它是否提供了松耦合?并且它能够被“ninjected”吗? - Callum Linington
@No1_Melman 将领域模型映射到ViewModels与耦合无关,也不必使用Ninjected。这只是确保AutoMapper的实现在一个地方而不是遍布在控制器中。 - Henk Mollema

1
如果你将视图模型作为独立项目,并在服务层处理映射和返回视图模型,我认为这样做没有问题。为了关注点分离,你可以另外创建一个组件来处理映射。

那将是另一个具有具体和接口实现的服务? - Callum Linington
是的。这将允许您将其注入到控制器中,并有助于单元测试。因为您可以独立测试服务,而不必担心每次在控制器中使用相同方法时进行测试。在控制器测试中,您只需模拟服务即可。 - Maess
我已经在问题中添加了一些信息,这是否仍与您的答案相符? - Callum Linington
我实现了一个服务,用于 1) 设置 ViewModel,2) 获取数据并保存到 Model。它适用于 EditCreate 操作,并且这意味着在控制器中只有一行代码 var entityToSave = _provider.CreateEntity(vm),这真是太棒了。感谢 @Maess。 - Callum Linington

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