【问题标题】:Selecting Instances of an Interface Based On Specific Value根据特定值选择接口实例
【发布时间】:2011-04-23 14:57:36
【问题描述】:

我将从这里开始介绍一些背景知识。我们有一个 ASP.Net MVC Web 应用程序,它位于一个大致基于Onion Architecture 概念的结构上。因此,我们有以下(简化的)垂直结构:

  • ASP.Net MVC 控制器层
  • 应用服务层
  • 业务服务层

注意:以上内容已简化,因为它不处理与此问题无关的视图、存储库、域对象等。

对于水平结构,我们有一些由我们称为“项目类型”定义的主要区域(为简单起见,本题将处理两个示例项目类型:“ItemTypeA”、“ItemTypeB”等)。

我们有一个业务服务接口,每个项目类型都有一个单独的实现:

public interface ISampleBusinessService
{
    string SampleMethod(string arg);
}

public class ItemTypeASampleBusinessService : ISampleBusinessService
{
    public string SampleMethod(string arg)
    {
        return "Item Type A: " + arg;
    }
}

public class ItemTypeBSampleBusinessService : ISampleBusinessService
{
    public string SampleMethod(string arg)
    {
        return "Item Type B: " + arg;
    }
}

上面是一个使用业务服务的应用服务:

public interface ISampleAppService
{
    string SampleMethod(string arg);
}

public class SampleAppService
{
    private readonly ISampleBusinessService service;

    public SampleAppService(ISampleBusinessService service)
    {
        this.service = service
    }

    public string SampleMethod(string arg)
    {
        return service.SampleMethod(arg);
    }
}

上面是我们使用应用服务的控制器:

public class SampleController : Controller
{
    private ISampelAppService service

    public SampleController(ISampleAppService service)
    {
        this.service = service;
    }

    public PartialViewResult SampleAction(string arg)
    {
        return PartialView( service.SampleMethod(arg) );
    }
}

请注意,控制器、应用程序服务接口和实现以及业务服务接口都是通用的——它们不关心正在使用的项目类型。但是,业务服务实现特定于项目类型。当我们调用控制器上的 action 方法(通过视图中的 RenderAction)时,我们知道我们正在处理的项目类型,但我们不确定确定使用哪个业务服务实现的最佳方法是什么。我们考虑了几个选项:

  1. 基类控制器并创建特定于项目类型的控制器继承者,然后与应用服务类似。这感觉像是一个弱解决方案 - 我们最终会编写一些在功能方面没有添加任何内容的类,除了确定我们正在处理的项目类型。
  2. 将标志向下传递到服务层并在工厂中创建服务实现(即,在其 CreateInstance 方法中采用“itemType”参数的 SampleBusinessServiceFactory)。这样做的问题是我们将一个变量向下传递了几个层,以便我们可以决定一个实现。到目前为止,我们一直在使用这种方法。
  3. 泛型 - 我们还没有真正考虑过这一点,但似乎也存在一些困难(如何从视图中的 ActionResult 调用中调用具有泛型的 Action 方法?)。从某种意义上讲,它类似于向下传递标志,但将基于强类型对象/服务,而不是使用枚举/魔术字符串。

哪种方法最适合解决这个问题?欢迎新的选择。

我们将不胜感激提供的任何帮助。

干杯,
扎克

【问题讨论】:

    标签: asp.net-mvc design-patterns architecture domain-driven-design


    【解决方案1】:

    这闻起来像是预先设计的大设计 (http://en.wikipedia.org/wiki/Big_Design_Up_Front)

    但是,它非常有可能同时调用控制器和操作方法: In asp.net mvc is it possible to make a generic controller?

    一些关于调用具有通用动作结果的动作的信息(产生相同的效果)。

    http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/12/12/enabling-ioc-in-asp-net-actionresults-or-a-better-actionresult.aspx

    我是这种方法的忠实拥护者,学习这些技术对任何想要保持控制器超薄的人都有帮助。


    评论答案:

    您实际上并不需要调用泛型操作方法,您只需要一种将泛型参数传递给控制器​​的方法。基本流程是将您的模型类型作为路由参数包含在内。您可以使用正确的 RouteValueDictionary 轻松调用通用渲染操作。这是我的一个旧项目的一些示例代码:

    我的通用控制器的开始:

        public GenericController()
        {
            TypeTranslator.Configure("Brainnom.Web.Model", "Brainnom.Web");
        }
    
        [UrlRoute(Path="admin/{modelType}/add", Order=9000)]
        public virtual ActionResult Add()
        {
            return View( new MODEL() );
        }
    
        [HttpPost]
        [UrlRoute(Path = "admin/{modelType}/add",  Order = 9000)]
        public virtual ActionResult Add( MODEL model, string modelType)
        {
            if (!ModelState.IsValid)
                return View(model);
    
            var postedModel = new MODEL();
    
            UpdateModel(postedModel);
    
            using (var session = new MongoSession())
            {
                session.Add(postedModel);
            }
    
            return RedirectToRoute("Browse", new { modelType });
        }
    

    还有我的 GenericControllerFactory(总有一天我需要重构它)

    using System;
    using System.Web.Mvc;
    using System.Web.Routing;
    
    namespace Brainnom.Web.Controllers
    {
        public class GenericControllerFactory : DefaultControllerFactory
        {
            protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
            {
                //the generic type parameter doesn't matter here
                if (controllerName.EndsWith("Co"))//assuming we don't have any other generic controllers here
                    return typeof(GenericController<>);
    
                return base.GetControllerType(requestContext, controllerName);
            }
    
            protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
            {
                //are we asking for the generic controller?
                if (requestContext.RouteData.Values.ContainsKey("modelType"))
                {
                    string typeName = requestContext.RouteData.Values["modelType"].ToString();
                    //magic time
                    return GetGenericControllerInstance(typeName, requestContext);
                }
    
                if (!typeof(IController).IsAssignableFrom(controllerType))
                    throw new ArgumentException(string.Format("Type requested is not a controller: {0}",controllerType.Name),"controllerType");
    
                return base.GetControllerInstance(requestContext, controllerType);
            } 
    
            /// <summary>
            /// Returns the a generic IController tied to the typeName requested.  
            /// Since we only have a single generic controller the type is hardcoded for now
            /// </summary>
            /// <param name="typeName"></param>
            /// <returns></returns>
            private IController GetGenericControllerInstance(string typeName, RequestContext requestContext)
            {
                var actionName = requestContext.RouteData.Values["action"];
    
                //try and resolve a custom view model
    
                Type actionModelType = Type.GetType("Brainnom.Web.Models." + typeName + actionName + "ViewModel, Brainnom.Web", false, true) ?? 
                    Type.GetType("Brainnom.Web.Models." + typeName + ",Brainnom.Web", false, true);
    
                Type controllerType = typeof(GenericController<>).MakeGenericType(actionModelType);
    
                var controllerBase = Activator.CreateInstance(controllerType, new object[0] {}) as IController;
    
                return controllerBase;
            }
        }
    }
    

    【讨论】:

    • 我可以看到设计看起来有点过头,但在我们的例子中这是非常合理的。我们正在开发一个平台,该平台将为大约 30 个网站和其他几个基于服务器的应用程序提供服务——它们都使用一个共享域层;我们需要一个真正可扩展的设计。我们决定的架构是经过大量讨论和调查后选择的。至于控制器中的泛型,您是否遇到过从 RenderAction 调用中调用泛型操作方法的方法?这就是我们需要能够做的...为您的回复干杯。
    • 有点晚了(我不再在我提出问题时所在的地方工作),但对我来说答案看起来很可靠。干杯,扎克
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-22
    • 2012-08-22
    • 1970-01-01
    相关资源
    最近更新 更多