【问题标题】:How to use interfaces in DTO with ASP.NET Web API and Unity?如何将 DTO 中的接口与 ASP.NET Web API 和 Unity 一起使用?
【发布时间】:2017-01-18 18:41:15
【问题描述】:

我目前正在使用 Unity 容器在现有 ASP.NET Web API 项目中实现依赖注入。

我已经设法通过配置依赖解析器将我的服务类注入到我的 API 控制器中。

但对于控制器功能,我必须使用数据传输对象 (DTO)。
在那个对象中,我找不到如何使用我的模型合同。

这是 Web API 控制器方法

[HttpPost]
[Route("api/application/save")]
public IHttpActionResult SaveApplication(ApplicationUpdateDTO applicationUpdate)
{
    // Inner code calling service methods expecting IApplication and
    // collections of ITag as parameters.
}

这是DTO定义

public class ApplicationUpdateDTO
{
    public IApplication Application { get; set; }
    public IEnumerable<int> DeletedTagIds { get; set; }
    public IEnumerable<ITag> AddedTags { get; set; }
    public IEnumerable<int> DeletedPlatformIds { get; set; }
    public IEnumerable<ITag> AddedPlatforms { get; set; }
}

结果是DTO本身被初始化了,但不是所有null的属性。

我理解为什么不能设置属性:接口不能被实例化,并且它不知道要使用哪些类。但是我的 Unity 容器可以,这要归功于注册。

  • 是否可以以某种方式使用此“链接”来初始化 DTO 属性?
  • 有更好的方法吗?

注意事项:

  • 如果我在 DTO 中使用我的接口实现,它显然可以正常工作。
  • 控制器方法接收一个与我的 DTO 相同的 JSON 对象。

编辑

我还通过引用this post尝试了ModelBinder的实现。
但是对于关于ValueProviderResult 的行,我得到了null 值。

为方便起见,以下是 Todd 在另一个问题中的回答:

public class CreateSomethingModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        string key = bindingContext.ModelName;
        ValueProviderResult val = bindingContext.ValueProvider.GetValue(key);
        if (val != null)
        {
            string s = val.AttemptedValue as string;
            if (s != null)
            {
                return new CreateSomething(){Title = s; UserId = new Guid(ControllerContext.HttpContext.Request.Headers["userId"]);}
            }
        }
        return null;
    }
}

我从问题的回答中得到的一个小区别是使用 System.Web.Http.ModelBinding.IModelBinder 而不是 MVC。

根据要求,这里是我的界面的摘录。
IApplication 界面

public interface IApplication
{
    /// <summary>
    /// Identifier of the application.
    /// </summary>
    int Id { get; set; }

    /// <summary>
    /// Name of the application.
    /// </summary>
    string Name { get; set; }

    /// <summary>
    /// Version of the application.
    /// </summary>
    string Version { get; set; }

    /// <summary>
    /// Tags associated to the application.
    /// </summary>
    ICollection<ITag> Tags { get; }
}

ITag 界面

public interface ITag
{
    /// <summary>
    /// Identifier of the tag.
    /// </summary>
    int Id { get; set; }

    /// <summary>
    /// Identifier of the application to which the tag is linked.
    /// </summary>
    int ApplicationId { get; set; }

    /// <summary>
    /// Value of the tag.
    /// </summary>
    string Value { get; set; }
}

JSON 示例:

{
    "marketApplication": {
      "Id": 20,
      "Name": "MyApplication",
      "Version": "2.0"
    },
    "deletedTagIds": [],
    "addedTags": [
      {
        "Id": 0,
        "Value": "NewTag"
      }
    ],
    "deletedProgramIds": [],
    "addedPrograms": [
      {
        "Id": 0,
        "Name": "x86"
      }
    ]
}

【问题讨论】:

  • 创建自定义模型绑定器以在绑定模型时解析合同实现。在那里您提取与您的属性相关的段并反序列化/解析然后分配给属性
  • @Nkosi 我有效地尝试在this topic 的帮助下创建一个ModelBinder,但我为ValueProviderResult (ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(key);) 检索了一个null。我虽然当时这不是我正在寻找的答案。
  • 举例说明接口、实现和发送的JSON
  • 你能解释一下为什么你的 DTO 使用接口而不是具体的类吗?接口的使用通常仅在需要抽象某些行为时才有用。抽象数据本身通常根本没有用(因为它只是数据)。从这个角度来看,直接使用具体类更有意义,并且可以通过 Web API 开箱即用地绑定模型。
  • 创建一个自定义模型绑定器,用于查找所请求接口的类型并对其进行水合。

标签: c# asp.net-web-api dependency-injection unity-container


【解决方案1】:

依赖注入是组合松耦合组件图的实践。组件是系统中包含行为的类。

依赖注入并不意味着构建仅包含数据的对象。使用依赖注入,我们构建了一个组件图。在构建该图之后(使用构造函数注入),我们使用方法调用通过该图传递运行时数据。

每次您尝试将依赖注入或 DI 容器(如 Unity)用于其他任何事情时,都会遇到麻烦。因此,尽管您的问题表明您想使用 Unity 执行此操作,但 Unity 应该被排除在等式之外(对于这种特殊情况)。

正如其他人已经说过的,构建通过请求传入的数据传输对象 (DTO) 是 Web API 模型绑定器的工作。默认的模型绑定器不能为你反序列化接口,这很明显;他们应该反序列化到什么实现?

虽然您可以替换默认模型绑定器,但您应该退后一步,仔细查看您想要实现的目标。您正在抽象化数据。将 DTO 隐藏在抽象后面通常没有什么意义,因为接口旨在抽象 行为

因此,与其使用接口,不如使用具体类来代替。

它将手动将副本从“sub-DTO”保存到具体的副本

不这样做,更简单的方法是使用 composition。您可以从较小的 DTO 中组合 DTO。这将使您不必完全复制。

使用在我的 Unity 容器中注册的匹配类型。

这假定这些 DTO 应该在容器中注册,但同样,DI 容器不应该保存任何运行时数据。这应该被排除在外。或如here所述:

在构建过程中不要将运行时数据注入到应用程序组件中;它会导致歧义,使组合根复杂化并承担额外的责任,并使验证 DI 配置的正确性变得异常困难。我的建议是让运行时数据通过构造对象图的方法调用流动。

更新

组合的想法很简单,你从更小的类构建类;而不是使用继承或复制对象结构。在您的情况下这看起来如何显然取决于您的需求,但我想您希望将 ITag 数据复制到另一个具有更多属性的类:

public class SomeObject
{
    // Members:
    public string Name { get; set; }
    public string Description { get; set; }

    // Members to copy from ITag
    public int Id { get; set; }
    public int ApplicationId { get; set; }
    public string Value { get; set; }

    // more members
}

相反,您可以撰写 SomeObject 从具体的Tag DTO:

public class SomeObject
{
    // Members:
    public string Name { get; set; }
    public string Description { get; set; }

    public Tag Tag { get; set; }

    // more members
}

这样你就不用复制Tag的成员了;您只需设置 Tag 属性并引用反序列化的 Tag DTO。

【讨论】:

  • 感谢您的解释。我同意你的看法。就像我在问题中写的那样,我理解为什么它不能实例化 DTO。我想要实现的是重用界面;我还没有为 DTO 创建它。另外,我想重用接口到容器中具体类的链接,而不是直接使用 DI。我从不想在容器中注册任何 DTO,这根本不相关,我承认。这个想法是为 DTO 的属性匹配具体类型。然后我会考虑创建“sub-DTO”。你能告诉我更多关于 composition 方法的信息吗?
  • @Niitaku:有关作曲的信息,请参阅我的更新。
  • 如果我理解得很好,它只是使用对象属性(如果适用)而不是公开所有属性?在我的情况下,我必须将数据从 DTO 传输到继承自 IApplication 的对象,该对象在 ITagIEnumerable 内。对于我当前的上下文,我分别有具体的类 ApplicationApplicationTag。我看不出如何使用TagDTO 对象设置属性ITag,如果它没有实现ITag,即使它具有相同的属性?这不是一个可疑的演员表吗?
  • @Niitaku:你还在谈论IApplicationITag 接口。这些接口不应该存在。它们毫无用处。
  • 对于 DTO 本身,他们没有。但是我的服务函数需要至少实现这些接口定义的属性的对象。这就是为什么我需要将数据从 DTO 检索到实现我的接口的对象中。我不在乎这些对象是否实现了其他属性,我只想确保它们具有对它们执行某些操作所需的最低要求。这也是接口的目的,不是吗?
猜你喜欢
  • 1970-01-01
  • 2012-12-10
  • 1970-01-01
  • 2016-01-09
  • 2014-10-29
  • 2015-10-10
  • 2018-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多