0

谁能解释一下创建松散耦合视图模型的理论。

我在下面附上了一些示例代码来尝试解释我的意思。

我有 2 个示例类仅用于此示例

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 等,只要所有方法等都匹配,代码仍然可以工作。

然后我创建一个视图模型

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";

        }
    }
} 

所以我的问题是三个部分。

  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>
    

至今

该雇主担任@Model.EmployerDetails.position 职位,性别为@Model.EmployerDetails.gender

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

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

谢谢

乔治

4

1 回答 1

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 的事件聚合器类,因为它很好地解决了你的耦合问题。这个库对于学习 MVVM 模式来说还是值得一看的。

The IEventAggregator allows you to subscribe with instance of a class to an instance of the aggregator. If multiple view models share an instance of the event aggregator you can easily send events from one to another in a loosley coupled way.

I refactored your original code, to make it more fitting for the actual MVVM pattern (your first question, let's say this implementaion is more proper). I've added an Employer class, which is basically the main object. It only has an id. The EmployerDetails and EmployerAddress also have a new property EmployerId which is a reference to the Employer they belong to.

I have put all the stuff to query data in the MyRepository class!

For each of those three classes exist three seperate view models and they're only coupled through the event aggregator they share (answers your 2nd question). The EmployerViewModel manages the main data objects of type Employer and publishes an event as soon as the selected Employer changes. The new value is passed into the EmployerChangedEvent which then is caught by the view models which handle this certain kind of event (IHandle<EmployerChangedEvent). In their Handle() implementation the passed employer is put into a private field of the receiving view model.

This is just a console application which simulates user input, though try with putting break points on both of the handle methods, as the SelectedEmployer changes.

I think some kind of stuff I do in my Main() method should be done in your controllers. I have to mention that this code is just for showing the benefits of the MVVM pattern, it might be over abstracted in some cases. Also things like querying the repository efficiently are not covered at all!

I think my answer also solves your 3rd question, as I see no foreach is anymore needed.

Remember to reference Caliburn.Micro if you'd like to run this code. Just get it through NuGet or download it here.

于 2013-04-08T12:19:56.920 回答