将特定形状的POCO返回到ASP.NET MVC操作

时间:2012-01-24 17:21:37

标签: asp.net-mvc soa api-design

在我的ASP.NET MVC项目中,我的操作通常调用Service层来获取数据。我为所有模特使用了相同的十几个POCO。我还计划在控制台应用程序中使用服务层,并可能在某些时候公开web api。

为了提高我的数据库操作效率,我的服务层只保留了模型中与特定方法相关的属性(此时主要由我的控制器操作的需要驱动)。

例如,我可能有一个属性Order的类Id, Name, Description, Amount, Items。对于给定的服务呼叫,我可能只需要填充Id, Name, Items。该服务的使用者不一定知道Amount仅为0,因为它没有填充该属性。

同样,消费者不会知道Items是否为空b / c实际上没有任何项目,或者这种特定服务方法是否只是不填充该属性。

对于第三个例子,假设我的一个视图显示ItemCount。我不想完全填充我的Items集合,我只需要在我的“模型”上添加一个属性。我不想将此属性添加到我的POCO中,其他服务方法将使用它,因为它不会在其他任何地方填充。

因此,自然的解决方案是只使用这3个属性制作专门为该方法设计的POCO。这样,消费者就可以知道所有属性都将填充其实际值。不利的一面是,我将结束大量类似形状的模型。

关于哪种方法最有效的建议?

8 个答案:

答案 0 :(得分:10)

您可以使用Nullable Types来指示缺少的属性。

例如:

class Order {
    public int Id {get;set;}
    public string Name {get;set;}
    public string Description {get;set;}
    public decimal? Amount {get;set;}
    public List<Item> Items {get;set;}
}

然后如果Items == null,则没有设置。如果它是空new List<Item>(),则设置为空。 Amount也是如此。如果Amount.HasValue == false,则未设置。如果Amount.Value为0.0d,则设置该项并且该项是免费的。

答案 1 :(得分:3)

为什么不使用LINQ投影?

一种服务方法可以做到:

return DbContext.Orders.Select(o => new { Id = o.Id, Name = o.Name, Description = o.Description });

而其他服务方法的确如下:

return DbContext.Orders.Select(o => o);

我不确定您的应用程序是如何构建的,但这可能是创建100个POCO的方法。

希望这有帮助!祝你好运。

答案 2 :(得分:1)

您可以传入一个返回Func的选择器dynamic

public IEnumerable<dynamic> GetOrders(Func<Order, dynamic> selector) { ... }

我不确定您是如何访问数据的,但以下显示了如何使用List<T>

class Program
{
    static void Main(string[] args)
    {
        var service = new Service();
        var orderNames = service.GetOrders(o => new { o.Name });

        foreach (var name in orderNames)
            Console.WriteLine(name.Name);

        Console.ReadLine();
    }
}

public class Service
{
    private List<Order> _orders = new List<Order>
        {
            new Order { Id = 1, Name = "foo", Description = "test order 1", Amount = 1.23m },
            new Order { Id = 2, Name = "bar", Description = "test order 1", Amount = 3.45m },
            new Order { Id = 3, Name = "baz", Description = "test order 1", Amount = 5.67m }
        };

    public IEnumerable<dynamic> GetOrders(Func<Order, dynamic> selector)
    {
        return _orders.Select(selector);
    }
}

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Amount { get; set; }
}

答案 3 :(得分:1)

使用可空值是一个很好的解决方案,但它有一个缺点,你无法匹配必填字段。也就是说,您无法在任何属性上使用必需属性。因此,如果在某些视图中存在必须使用的字段,则无法表示它。 如果您不需要必要的文件验证,这是可以的。否则,您需要一种方法来表示实际使用的文件,然后编写自定义验证提供程序。

执行此操作的一种简单方法是使用具有相同属性名称的“Mask”类,但所有字段都为boolean:true值表示该字段正在使用中。

我在一个系统中使用了类似的解决方案,其中要显示的属性在配置文件中配置...所以它是我的唯一选项,因为我不可能代表所有属性组合。但是,我在View中也使用了“Mask”类,所以我只用一个View就可以完成所有工作..有很多ifs。

现在,如果你的150个服务方法和大约150个视图......都不同,那么也许使用几个类更简单......在最坏的情况下150个类......编写它们的额外工作与准备150个不同视图的努力相比,可以忽略不计。 然而,这并不意味着你需要150个POCO课程。您可以使用唯一的POCO类,将其复制到表示层中的适当类中。这种方法的优点是您可以在各种类上放置不同的验证属性,而不需要编写自定义验证提供程序。

答案 4 :(得分:1)

使用@sbolm提到的可空类型返回整个POCO。然后,您可以为每个MVC页面视图创建一个ViewModel,该视图接收具有所需特定属性的模型。这将带来更多性能(无关紧要)和代码,但它可以保持您的服务层清洁,并使您的视图“愚蠢”,因为它们只是给予他们所需要的,并且与服务层没有直接关系。

即。 (来自@sbolm的示例类)

class Order {
    public int Id {get;set;}
    public string Name {get;set;}
    public string Description {get;set;}
    public decimal? Amount {get;set;}
    public List<Item> Items {get;set;}
}

// MVC View only needs to know the name and description, manually "map" the POCO properties into this view model and send it to the view

class OrderViewModel {
    public string Name {get;set;}
    public string Description {get;set;}
}

答案 5 :(得分:0)

我建议您不必修改模型或创建包装模型,而是必须将服务方法命名为不言自明,并向消费者展示他们返回的内容。

可空方法的问题是它让用户感觉属性不是必需的或强制的,并且他们尝试插入这些类型的实例而不设置这些属性。 在每个地方都没有错误会不会很糟糕?

更改域模型不是一个好方法,因为您只需要填充一些属性,而不是使用不言自明的名称和描述创建服务。

Order类本身为例,假设一个服务方法返回包含所有项目的Order,另一个服务方法仅返回Order但不返回项目的详细信息。那么显然你可能需要创建两个服务方法GetOrderItemsGetOrderDetail,这听起来很简单,是的!但请注意,服务方法名称本身告诉客户端它将返回什么。在GetOrderDetail中你可以返回一个空项或null(但在这里我建议为null)并不重要。

因此,对于新案例,您不需要经常更改模型,但您需要做的就是添加或删除服务方法,这很好。由于您正在创建服务,因此您可以创建一个强大的文档,说明使用什么方法

答案 6 :(得分:0)

除非你真的遇到性能问题,否则我不会对性能进行优化。

我只会区分使用更完整的对象图返回平面对象和对象。

我会让方法返回称为GetOrder,GetProduct等的扁平对象。

如果请求更完整的对象图,则会调用它们:GetOrderWithDetails。

您是否对输入的视图使用POCO类?如果是:尝试创建用作专用ViewModel的新类。这些ViewModel将包含POCO类。这将有助于您保持POCO课程的清洁。

答案 7 :(得分:0)

要扩展可空的想法,您可以使用fluentvalidation库来依赖于它们是否为null来对类型进行验证。只要它不是null或您可以想到的任何其他验证方案,这将允许您需要一个字段。我自己的代码示例,因为我有类似的要求:

Imports FluentValidation

Public Class ParamViewModelValidator
    Inherits AbstractValidator(Of ParamViewModel)

    Public Sub New()
        RuleFor(Function(x) x.TextBoxInput).NotEmpty.[When](Function(x) Not (IsNothing(x.TextBoxInput)))
        RuleFor(Function(x) x.DropdownListInput).NotEmpty.[When](Function(x) Not (IsNothing(x.DropdownListInput)))
    End Sub

End Class