松散耦合视图模型的理论

时间:2013-04-08 11:02:38

标签: c# asp.net-mvc razor asp.net-mvc-4

任何人都可以解释创建松散耦合视图模型的理论。

我附上了一些示例代码,试图解释我的意思。

我只为此示例提供了两个示例类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Web.UI.Models
{
    public class EmployerAddress
    {
        public string address { get; set; }
        public string city { get; set; }
        public string region { get; set; }
        public string country { get; set; }
        public string postZipCode { get; set; }
    }

    public class EmployerDetails
    {
        public string position { get; set; }
        public string gender { get; set; }
        public string dob { get; set; }
    }

    public class DisplayEmployerAddress : IDisplayEmployerAddress
    {
        public IEnumerable<EmployerAddress> employerAddr()
        {
            List<EmployerAddress> Data = new List<EmployerAddress>();
            Data.Add(new EmployerAddress
            {
                address = "address1",
                city = "city1",
                region = "region1",
                country = "country1",
                postZipCode = "post zip1"
            });
            return Data;
        }
    }

    public class DisplayEmployerDetails : IDisplayEmployerDetails
    {
        public IEnumerable<EmployerDetails> employerDetails()
        {
            List<EmployerDetails> Data = new List<EmployerDetails>();
            Data.Add(new EmployerDetails
            {
                position = "trainee",
                gender = "male",
                dob = "22-08-1964"
            });
            Data.Add(new EmployerDetails
            {
                position = "trainee2",
                gender = "male2",
                dob = "22-08-1970"
            });
            return Data;
        }
    }
}

上面的代码有接口:

IEnumerable<EmployerAddress> employerAddr();
IEnumerable<EmployerDetails> employerDetails();

然后我使用Ninject来绑定上面的内容。

kernel.Bind<IDisplayEmployerAddress>().To<DisplayEmployerAddress>().InSingletonScope();
kernel.Bind<IDisplayEmployerDetails>().To<DisplayEmployerDetails>().InSingletonScope();

此时一切正常,我可以更改DisplayEmployerAddress等,只要所有方法等匹配代码仍然有效。

然后我创建了一个viewmodel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Web.UI.Models
{
    public class EmployerDetailsViewModel
    {
        public string age { get; set; }
        public IEnumerable<EmployerAddress> EmployerAddress { get; set; }
        public IEnumerable<EmployerDetails> EmployerDetails { get; set; }
    }
}

但是现在这会导致问题,因为EmployerAddress现在紧密耦合,所以如果我更改代码,它现在必须在2个地方更新。

在我的控制器中我有

public class HomeController : Controller
    {
        private readonly IDisplayEmployerAddress _address;
        private readonly IDisplayEmployerDetails _details;

        public HomeController(IDisplayEmployerAddress address,
                              IDisplayEmployerDetails details)
        {
            _address = address;
            _details = details;
        }
        public ActionResult Index()
        {
            ViewBag.Title = "Title";
            var Address = _address.employerAddr();
            var Details = _details.employerDetails().AsEnumerable();
            var Age = _details.employerDetails().FirstOrDefault().dob;
            var employerModel = new EmployerDetailsViewModel
            {
                EmployerAddress = Address,
                EmployerDetails = Details,
                age = age.calAge(Age)
            };
            return View(employerModel);
        }

我保持控制器的轻量级,因为我读过的所有书都说在控制器中保留尽可能少的代码,所以为了计算年龄,我使用静态类。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Web.UI.Models
{
    public static class age
    {
        public static string calAge(string dob)
        {
            //Would cal age here
            return "48";

        }
    }
} 

所以我的问题是3部分。

  1. 这个例子是正确的方法。
  2. 由于我的视图模型现在紧密耦合,我如何使它变得松散 耦合。
  3. 如果我不想使用foreach循环,我怎样才能获得每个项目 说EmployerDetails

    该雇主持有@ Model.EmployerDetails.position的职位,性别为@ Model.EmployerDetails.gender

    <ul>
    @foreach (var d in Model.EmployerAddress)
    {
       <li>@d.address</li>
        <li>@d.city</li>
        <li>@d.country</li>
        <li>@d.region</li>
        <li>@d.postZipCode</li> 
    }
        </ul>
    
    <ul>
    @foreach (var dd in Model.EmployerDetails)
    {
       <li>@dd.position</li>
        <li>@dd.gender</li>
        <li>@dd.dob</li>
    }
        </ul>
    
  4. 到目前为止

    该雇主持有@ Model.EmployerDetails.position的职位,性别为@ Model.EmployerDetails.gender

    解决问题3,将代码更改为 @ Model.EmployerDetails.FirstOrDefault()。position

    希望上面的例子对我正在学习的东西有意义

    由于

    乔治

1 个答案:

答案 0 :(得分:1)

public class Employer
{
    public int Id { get; set; }
}

public class EmployerAddress
{
    public string Address { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string Country { get; set; }
    public string PostZipCode { get; set; }

    public int EmployerId { get; set; }
}

public class EmployerDetails
{
    public string Position { get; set; }
    public string Gender { get; set; }
    public string Dob { get; set; }

    public int EmployerId { get; set; }
}

public class MyRepository : IMyRepository
{
    public IEnumerable<Employer> GetEmployers()
    {
        return new List<Employer>
            {
                new Employer {Id = 1},
                new Employer {Id = 2}
            };
    }

    public IEnumerable<EmployerAddress> GetEmployeeAddresses()
    {
        return new List<EmployerAddress>
            {
                new EmployerAddress
                    {
                        EmployerId = 1,
                        Address = "address1",
                        City = "city1",
                        Region = "region1",
                        Country = "country1",
                        PostZipCode = "post zip1"
                    },
                new EmployerAddress
                    {
                        EmployerId = 2,
                        Address = "address2",
                        City = "city2",
                        Region = "region2",
                        Country = "country2",
                        PostZipCode = "post zip2"
                    }
            };
    }

    public IEnumerable<EmployerDetails> GetEmployeeDetails()
    {
        return new List<EmployerDetails>
            {
                new EmployerDetails
                    {
                        EmployerId = 1,
                        Position = "trainee",
                        Gender = "male",
                        Dob = "22-08-1964"
                    },
                new EmployerDetails
                    {
                        EmployerId = 2,
                        Position = "trainee2",
                        Gender = "male2",
                        Dob = "22-08-1970"
                    }
            };
    }
}

public class EmployerChangedEvent
{
    public EmployerChangedEvent(Employer selectedEmployer)
    {
        Employer = selectedEmployer;
    }

    public Employer Employer { get; set; }
}

public class EmployerViewModel
{
    private readonly IEventAggregator _events;
    private Employer _selectedEmployer;

    // Configure Ninject properly to get those types
    public EmployerViewModel(IEventAggregator events, IMyRepository myRepository)
    {
        _events = events;
        Employers = myRepository.GetEmployers().ToList();
        EmployerAddressViewModel = new EmployerAddressViewModel(_events, myRepository);
        EmployerDetailsViewModel = new EmployerDetailsViewModel(_events, myRepository);
    }

    public List<Employer> Employers { get; set; }

    public EmployerAddressViewModel EmployerAddressViewModel { get; set; }
    public EmployerDetailsViewModel EmployerDetailsViewModel { get; set; }

    public Employer SelectedEmployer
    {
        get { return _selectedEmployer; }
        set
        {
            _selectedEmployer = value;
            // this notifies the dependent view models in a loosley coupled way
            _events.Publish(new EmployerChangedEvent(_selectedEmployer));
        }
    }
}

public class EmployerAddressViewModel :
    IHandle<EmployerChangedEvent> // specifies which events shall be caught
{
    private readonly IMyRepository _myRepository;
    private Employer _selectedEmployer;

    public EmployerAddressViewModel(IEventAggregator events, IMyRepository myRepository)
    {
        _myRepository = myRepository;
        // this subscribes this view model to the passed event aggregator
        // from your main view model (EmployerViewModel)
        events.Subscribe(this);
    }

    public EmployerAddress EmployerAddress { get; set; }

    public void Handle(EmployerChangedEvent message)
    {
        _selectedEmployer = message.Employer;
        EmployerAddress = _myRepository.GetEmployeeAddresses()
                                       .FirstOrDefault(e => e.EmployerId == _selectedEmployer.Id);
    }
}

public class EmployerDetailsViewModel :
    IHandle<EmployerChangedEvent> // specifies which events shall be caught
{
    private readonly IMyRepository _myRepository;
    private Employer _selectedEmployer;

    public EmployerDetailsViewModel(IEventAggregator events, IMyRepository myRepository)
    {
        _myRepository = myRepository;
        // this subscribes this view model to the passed event aggregator
        // from your main view model (EmployerViewModel)
        events.Subscribe(this);
    }

    public EmployerDetails EmployerDetails { get; set; }

    public void Handle(EmployerChangedEvent message)
    {
        _selectedEmployer = message.Employer;
        EmployerDetails = _myRepository.GetEmployeeDetails()
                                       .FirstOrDefault(e => e.EmployerId == _selectedEmployer.Id);
    }
}

internal class Program
{
    private static void Main(string[] args)
    {
        // do this with Ninject
        var employerViewModel = new EmployerViewModel(new EventAggregator(), new MyRepository());

        // this selection should actually be user input
        employerViewModel.SelectedEmployer = employerViewModel.Employers.First();

        // select another one
        employerViewModel.SelectedEmployer = employerViewModel.Employers.Last();
    }
}

由于我不熟悉ASP.NET,我的回答并不意味着任何UI通知。

我建议Caliburn.Micro的event aggregator课,因为它可以很好地解决你的耦合问题。无论如何,这个库值得一看,以学习MVVM模式。

IEventAggregator允许您将类的实例订阅到聚合器的实例。如果多个视图模型共享事件聚合器的实例,则可以通过loosley耦合方式轻松地将事件从一个发送到另一个。

我重构了你的原始代码,使其更适合实际的MVVM模式(你的第一个问题,让我们说这个实现更合适)。我添加了一个Employer类,它基本上是主要对象。它只有一个id。 EmployerDetailsEmployerAddress也有一个新属性EmployerId,它是对所属Employer的引用。

我已将所有内容都放在MyRepository类中查询数据!

对于这三个类中的每一个都存在三个单独的视图模型,它们仅通过它们共享的事件聚合器耦合(回答您的第二个问题)。 EmployerViewModel管理Employer类型的主要数据对象,并在所选Employer更改后立即发布事件。新值将传递到EmployerChangedEvent,然后由处理此特定类型事件(IHandle<EmployerChangedEvent)的视图模型捕获。在Handle()实施中,将通过的雇主放入接收视图模型的私有字段中。

这只是一个模拟用户输入的控制台应用程序,但尝试在两个句柄方法上设置断点,SelectedEmployer更改。

我认为我在Main()方法中所做的某些事情应该在你的控制器中完成。我必须提到这段代码只是为了展示MVVM模式的好处,在某些情况下它可能过于抽象。此外,根本没有涉及有效查询存储库的事情!

我认为我的回答也解决了你的第3个问题,因为我看不再需要foreach

如果您想运行此代码,请记得引用Caliburn.Micro。只需通过NuGet获取它或下载它here