3

我是一个孤独的开发人员,Pluralsight 是我的救星,它帮助我理解了 Repository 和 IoC 等我刚刚学习的东西。但是我发现这种模式使用起来很尴尬。

所以我正在使用 Unity IoC 和以下ModelContainer课程;

public static class ModelContainer
{
    private static IUnityContainer instance;

    static ModelContainer()
    {
        instance = new UnityContainer();
    }

    public static IUnityContainer Instance
    {
        get
        {
            instance.RegisterType<ISCD_CallDiaryRepository, SCD_CallDiaryRepository>(new HierarchicalLifetimeManager());
            instance.RegisterType<ICompanyRepository, CompanyRepository>(new HierarchicalLifetimeManager());
            instance.RegisterType<ISubcontractorRepository, SubcontractorRepository>(new HierarchicalLifetimeManager());
            instance.RegisterType<ITradeRepository, TradeRepository>(new HierarchicalLifetimeManager());
            instance.RegisterType<IEmployeeRepository, EmployeeRepository>(new HierarchicalLifetimeManager());
            instance.RegisterType<ISubcontractorTradeRepository, SubcontractorTradeRepository>(new HierarchicalLifetimeManager());
            instance.RegisterType<ICountyRepository, CountyRepository>(new HierarchicalLifetimeManager());
            instance.RegisterType<IAddressLineRepository, AddressLineRepository>(new HierarchicalLifetimeManager());
            return instance;
        }
    }
}

允许我在控制器的构造函数中实例化存储库类;

public SubcontractorController(
        ISubcontractorRepository subcontractorRepository,
        ISubcontractorTradeRepository subcontractorTradeRepository, 
        ICompanyRepository companyRepository, 
        ISCD_CallDiaryRepository scdCallDiaryRepository,
        ITradeRepository tradeRepository,
        IAddressLineRepository addressLineRepository)
    {
        this.SubcontractorRepository = subcontractorRepository;
        this.SubcontractorTradeRepository = subcontractorTradeRepository;
        this.CompanyRepository = companyRepository;
        this.ScdCallDiaryRepository = scdCallDiaryRepository;
        this.TradeRepository = tradeRepository;
        this.AddressLineRepository = addressLineRepository;
    }

我对此设置感到尴尬的是,如果我想在这些存储库类中使用 CRUD 功能,我必须在控制器中执行该功能,或者将我创建的存储库实例作为参数传递给模型类方法。我选择后者,控制器并不意味着做太多事情,但对我来说,必须不断传递存储库参数似乎是不对的。

我错过了什么?

4

3 回答 3

5

我将我的 MVC 应用程序分成几个不同的项目。

  • AppName.Configuration:处理应用程序的任何配置(即拉入 web.config/app 设置等)
  • AppName.Data:这是执行所有数据库访问的数据层(无业务逻辑)。DBML/EDMX 在这里,我的存储库类也在这里。
  • AppName.Models:这是我为 MVC 定义的所有 ViewModel 以及整个应用程序所需的其他模型对象的地方。
  • AppName.Services:这是我的业务层,一切都必须经过这里才能到达数据层或表示层。ViewModels 是从数据库对象构建的,数据验证发生在这里,等等。
  • AppName.Web:这将是 MVC 应用程序。AppName.Data.Test:数据应用的单元测试
  • AppName.Services.Test:服务的单元测试
  • AppName.Web.Test:MVC 控制器的单元测试
  • AppName.Web.UI.Test:Web 用户界面的单元测试(使用 WATIN)

我还有一组打包到 NuGet 包中的类,如果需要,我可以将它们添加到我的应用程序中,即(对于本示例):

  • CompanyName.Data:数据层逻辑的通用库
  • CompanyName.MVC:用于 ASP.NET MVC 集成的通用库
  • CompanyName.Utilities:杂项实用程序的公共库

我的控制器除了从服务层获取视图模型以发送到视图,然后在从视图发布时接收数据并将其发送到服务层进行验证并保存回存储库之外什么都不做。

这是一个基本示例:

这是将在此示例中使用的视图模型:

public class CreateFocusViewModel
{
    public int CareerPlanningFormID { get; set; }

    public int PerformanceYear { get; set; }

    public IList<FocusModel> Focuses { get; set; }

    public string ResultsMeasuresFocusComments { get; set; }

    public byte MaximumFocusesAllowed { get; set; }
}

public class FocusModel
{
    public int FocusID { get; set; }

    public string FocusText { get; set; }

    public bool IsPendingDeletion { get; set; }
}

带有 GET 和 POST 操作方法的示例控制器:

public class CPFController : Controller
{
    private readonly ICareerPlanningFormService careerPlanningFormService;

    public CPFController(ICareerPlanningFormService careerPlanningFormService)
    {
        this.careerPlanningFormService = careerPlanningFormService;
    }

    [HttpGet]
    public ViewResult CreateFocus(int careerPlanningFormID)
    {
        var model = this.careerPlanningFormService.BuildCreateFocusViewModel(careerPlanningFormID);
        return this.View(model);
    }

    [HttpPost]
    public ActionResult CreateFocus(int careerPlanningFormID, string button)
    {
        var model = this.careerPlanningFormService.BuildCreateFocusViewModel(careerPlanningFormID);
        this.TryUpdateModel(model);

        switch (button)
        {
            case ButtonSubmitValues.Next:
            case ButtonSubmitValues.Save:
            case ButtonSubmitValues.SaveAndClose:
                {
                    if (this.ModelState.IsValid)
                    {
                        try
                        {
                            this.careerPlanningFormService.SaveFocusData(model);
                        }
                        catch (ModelStateException<CreateFocusViewModel> mse)
                        {
                            mse.ApplyTo(this.ModelState);
                        }
                    }

                    if (!this.ModelState.IsValid)
                    {
                        this.ShowErrorMessage(Resources.ErrorMsg_WEB_ValidationSummaryTitle);
                        return this.View(model);
                    }

                    break;
                }

            default:
                throw new InvalidOperationException(string.Format(Resources.ErrorMsg_WEB_InvalidButton, button));
        }

        switch (button)
        {
            case ButtonSubmitValues.Next:
                return this.RedirectToActionFor<CPFController>(c => c.SelectCompetencies(model.CareerPlanningFormID));

            case ButtonSubmitValues.Save:
                this.ShowSuccessMessage(Resources.Msg_WEB_NotifyBarSuccessGeneral);
                return this.RedirectToActionFor<CPFController>(c => c.CreateFocus(model.CareerPlanningFormID));

            case ButtonSubmitValues.SaveAndClose:
            default:
                return this.RedirectToActionFor<UtilityController>(c => c.CloseWindow());
        }
    }
}

构建 ViewModel 并验证/保存数据的服务层:

public class CareerPlanningFormService : ICareerPlanningFormService
{
    private readonly IAppNameRepository repository;
    private readonly IPrincipal currentUser;

    public CareerPlanningFormService(IAppNameRepository repository, IPrincipal currentUser)
    {
        this.repository = repository;
        this.currentUser = currentUser;
    }

    public CreateFocusViewModel BuildCreateFocusViewModel(int careerPlanningFormID)
    {
        var cpf = this.repository.GetCareerPlanningFormByID(careerPlanningFormID);

        // create the model using cpf
        var model = new CreateFocusViewModel
        {
            CareerPlanningFormID = cpf.CareerPlanningFormID,
            PerformanceYear = cpf.PerformanceYearID,
            ResultsMeasuresFocusComments = cpf.ResultsMeasuresFocusComments,
            MaximumFocusesAllowed = cpf.PerformanceYear.MaximumCareerPlanningFormFocusesAllowed
            // etc., etc...
        };

        return model;
    }

    public void SaveFocusData(CreateFocusViewModel model)
    {
        // validate the model
        this.ValidateCreateFocusViewModel(model);

        // get the current state of the CPF
        var cpf = this.repository.GetCareerPlanningFormByID(model.CareerPlanningFormID);

        // bunch of code saving focus data here...

        // update the ResultsMeasuresFocusComments
        cpf.ResultsMeasuresFocusComments = string.IsNullOrWhiteSpace(model.ResultsMeasuresFocusComments) ? null : model.ResultsMeasuresFocusComments.Trim();

        // commit the changes
        this.repository.Commit();
    }

    private void ValidateCreateFocusViewModel(CreateFocusViewModel model)
    {
        var errors = new ModelStateException<CreateFocusViewModel>();

        {
            var focusesNotPendingDeletion = model.Focuses.Where(f => f.IsPendingDeletion == false);

            // verify that at least one of the focuses (not pending deletion) has a value
            {
                var validFocuses = focusesNotPendingDeletion.Where(f => !string.IsNullOrWhiteSpace(f.FocusText)).ToList();
                if (!validFocuses.Any())
                {
                    var index = model.Focuses.IndexOf(model.Focuses.Where(f => f.IsPendingDeletion == false).First());
                    errors.AddPropertyError(m => m.Focuses[index].FocusText, Resources.ErrorMsg_CPF_OneFocusRequired);
                }
            }

            // verify that each of the focuses (not pending deletion) length is <= 100
            {
                var focusesTooLong = focusesNotPendingDeletion.Where(f => f.FocusText != null && f.FocusText.Length > 100).ToList();
                if (focusesTooLong.Any())
                {
                    focusesTooLong.ToList().ForEach(f =>
                    {
                        var index = model.Focuses.IndexOf(f);
                        errors.AddPropertyError(m => m.Focuses[index].FocusText, Resources.ErrorMsg_CPF_FocusMaxLength);
                    });
                }
            }
        }

        errors.CheckAndThrow();
    }
}

存储库类:

public class AppNameRepository : QueryRepository, IAppNameRepository
{
    public AppNameRepository(IGenericRepository repository)
        : base(repository)
    {
    }

    public CareerPlanningForm GetCareerPlanningFormByID(int careerPlanningFormID)
    {
        return this.Repository.Get<CareerPlanningForm>().Where(cpf => cpf.CareerPlanningFormID == careerPlanningFormID).Single();
    }
}

仓库界面:

public interface IAppNameRepository : IRepository
{
    CareerPlanningForm GetCareerPlanningFormByID(int careerPlanningFormID);
}

CompanyName.Data 公共库中的类:

public abstract class QueryRepository : IRepository
{
    protected readonly IGenericRepository Repository;

    protected QueryRepository(IGenericRepository repository)
    {
        this.Repository = repository;
    }

    public void Remove<T>(T item) where T : class
    {
        this.Repository.Remove(item);
    }

    public void Add<T>(T item) where T : class
    {
        this.Repository.Add(item);
    }

    public void Commit()
    {
        this.Repository.Commit();
    }

    public void Refresh(object entity)
    {
        this.Repository.Refresh(entity);
    }
}

public interface IGenericRepository : IRepository
{
    IQueryable<T> Get<T>() where T : class;
}

public interface IRepository
{
    void Remove<T>(T item) where T : class;
    void Add<T>(T item) where T : class;
    void Commit();
    void Refresh(object entity);
}

我有 LinqToSQL 和 EF,这是 LinqToSQL 的设置:

internal sealed class LinqToSqlRepository : IGenericRepository
{
    private readonly DataContext dc;

    public LinqToSqlRepository(DataContext dc)
    {
        this.dc = dc;
    }

    public IQueryable<T> Get<T>() where T : class
    {
        return this.dc.GetTable<T>();
    }

    public void Remove<T>(T item) where T : class
    {
        this.dc.GetTable<T>().DeleteOnSubmit(item);
    }

    public void Add<T>(T item) where T : class
    {
        this.dc.GetTable<T>().InsertOnSubmit(item);
    }

    public void Commit()
    {
        this.dc.SubmitChanges();
    }

    public void Refresh(object entity)
    {
        this.dc.Refresh(RefreshMode.OverwriteCurrentValues, entity);
    }
}

这也在 CompanyName.Data 公共库中。它具有注册 LinqToSQL 或 EntityFramework 的方法

public static class UnityContainerExtensions
{
    public static IUnityContainer RegisterEntityFrameworkClasses<TDbContext>(this IUnityContainer container, string nameOrConnectionString) where TDbContext : DbContext
    {
        var constructor = typeof(TDbContext).GetConstructor(new Type[] { typeof(string) });
        container.RegisterType<DbContext>(new HierarchicalLifetimeManager(), new InjectionFactory(c => constructor.Invoke(new object[] { nameOrConnectionString })));
        container.RegisterType<IGenericRepository, EntityFrameworkRepository>();
        return container;
    }

    public static IUnityContainer RegisterLinqToSqlClasses<TDataContext>(this IUnityContainer container, string connectionString) where TDataContext : DataContext
    {
        var constructor = typeof(TDataContext).GetConstructor(new Type[] { typeof(string) });
        container.RegisterType<DataContext>(new HierarchicalLifetimeManager(), new InjectionFactory(c => constructor.Invoke(new object[] { connectionString })));
        container.RegisterType<IGenericRepository, LinqToSqlRepository>();
        return container;
    }
}

在 CompanyName.Utilities 库中:

public interface IUnityBootstrap
{
    IUnityContainer Configure(IUnityContainer container);
}

AppName.Data 中的 Unity 引导

public class UnityBootstrap : IUnityBootstrap
{
    public IUnityContainer Configure(IUnityContainer container)
    {
        var config = container.Resolve<IAppNameConfiguration>();

        return container.RegisterLinqToSqlClasses<AppNameDataContext>(config.AppNameConnectionString)
                        .RegisterType<IAppNameRepository, AppNameRepository>();
    }
}

AppName.Services 中的 Unity 引导

public class UnityBootstrap : IUnityBootstrap
{
    public IUnityContainer Configure(IUnityContainer container)
    {
        new CompanyName.Security.UnityBootstrap().Configure(container);
        new AppName.Data.UnityBootstrap().Configure(container);

        container.RegisterSecureServices<AuthorizationRulesEngine>(typeof(UnityBootstrap).Assembly);

        return container.RegisterType<ICareerPlanningFormService, CareerPlanningFormService>()
                        .RegisterType<IStaffService, StaffService>();
    }
}

AppName.Web 中的 Unity 引导

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        // Standard MVC setup
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);

        // Application configuration 
        var container = new UnityContainer();
        new CompanyName.Mvc.UnityBootstrap().Configure(container);
        new AppName.Configuration.UnityBootstrap().Configure(container);
        new AppName.Data.UnityBootstrap().Configure(container);
        new AppName.Services.UnityBootstrap().Configure(container);

        // Default MVC model binder is pretty weak with collections
        ModelBinders.Binders.DefaultBinder = new DefaultGraphModelBinder();
    }

    protected void Application_Error()
    {
        HttpApplicationEventHandler.OnError(this.Context);
    }

    protected void Application_EndRequest()
    {
        HttpApplicationEventHandler.OnEndRequest(this.Context);
    }
}
于 2013-01-22T11:31:38.480 回答
5

In provided sample, there is SubcontractorController controller with six dependecies, what is usually means that the controller tries to do too much at once, and could possibly violates Single responsibility principle. There are at least three way to improve situation:

  • Instead of injecting repositories into a controller, and passing the repository instance as a parameter into the model class methods, you could refactor to Aggregate Services, i.e. combine both models method and dependencies in single entity. This will also solve constructor over-injection.
  • Inject a factory of repositories into the controller, and later resolve dependencies using this factory
  • Inject models themselves, and make repositories to be their dependencies.
于 2013-01-22T09:29:41.630 回答
1

在我职业生涯的早期,我也尝试过后者。结局并不好。您最终会遇到意外的性能下降和大型模型对象。我认为控制器是您提到的逻辑的理想场所。如果您担心同一类中的用户界面和业务问题,请将业务转移到服务类。

于 2013-01-22T08:10:17.577 回答