REST API(ASP.NET Web API 2)最佳实践:如何返回1-N个不是一种类型而是三种相关类型的项目?

cha*_*r m 5 c# rest restful-architecture asp.net-web-api2

我有3种类型如下.不同的模型可以有不同的属性.属性是代码描述属性,意味着它是有限的值集.

CarModel
{
   int Id;
   string Name;
}

CarAttributeType
{
     int Id;
     int ModelId;
     string GroupName;
     int DataOrder;
     bool Mandatory;
     // default values etc.
}

CarCodeDescriptions
{
    int Id;
    int AttributeTypeId;
    int Code;
    int Descr;
}
Run Code Online (Sandbox Code Playgroud)

要将这种模型用于实际汽车,最后会有解释.

好.现在我希望客户端可以获取,添加和更新整个模型的GET,POST和PUT操作.我是否制作了包含所有这些内容的类型

CarModelContainer
{
    CarModel Model;
    IEnumerable<CarAttributeType> Attributes;
    IEnumerable<CarCodeDescriptions> Attributes;
}
Run Code Online (Sandbox Code Playgroud)

然后有:

[Route("carmodels/{id:int}"")]
public CarModelContainer Get(int id)
{
    return Persistence.Instance.GetCarModel(id);
}

[Route("carmodels")]
public IEnumerable<CarModelContainer> GetAll()
{
    return Persistence.Instance.GetCarModels();
}
Run Code Online (Sandbox Code Playgroud)

或者是否希望在不制作此容器类的情况下以某种方式执行此操作(输出参数或其他内容)?我真的迷失在这里.

要使用实际车型:

Car
{
    int Id;
    int ModelId;
    //ETC
}

CarAttribute
{
    int Id;
    int CarId;
    int CatAttributeTypeId
    int CodeValue;
}
Run Code Online (Sandbox Code Playgroud)

Jef*_*ins 14

原始模型的问题在于这三个类实际上并没有通过引用进行链接.

我猜你想要一些东西.

public abstract class CarModel
{
     int id;
     string manufacturer;
     string name;
     List<CarAttributeType> attributes; // 0..n

}

public class CarModelAttribute
{
     int id;
     string name;
     CodifiedValue value; // 0..1 or do we want 0..* i.e. put it in a list
     int dataOrder; // no idea what this is doing
     bool mandatory;
}

public class CodifiedValue
{
     int id;
     int code;
     string description;
}

public class Car
{
     int id;
     string registration;
     int mileage;
     CarModel model;

     public Car(registration, mileage, model) {...}
}
Run Code Online (Sandbox Code Playgroud)

代码中的某个位置,您要存储静态汽车模型

readonly CarModel toyotaPrius = new CarModel("Toyota", "Prius", priusAttributes)

Car myToyota = new Car("DF01 1DXM", 1000, toyotaPrius)
Run Code Online (Sandbox Code Playgroud)

现在,如果您正在使用ORM,这意味着您将加载子项(如果它们通常默认为1:1,或者如果它们是0:n则可能是懒惰的).

这可能导致MASSIVE对象图被加载并编组为JSON/XML

实际上,您希望您的编组人员仅创建解耦参考,例如,可能是HATEOS样式

GET /myApp/car/3 (where 3 is the ID of my Prius), which would get you

"Car"
{
"url" : "/myApp/car/3",
"id" : 3,
"registration" : "DF01 1DXM",
"mileage": 1000,
"model" : "/myApp/carModel/54",       // where 54 maps back to the Id for the Toyota Prius
}
Run Code Online (Sandbox Code Playgroud)

如果您希望查看模型的详细信息,则可以执行GET/myApp/carModel/54.

这意味着您的GET/PUT/POST等不需要水合或处理大型对象图.如果你想检索整个图,你可以使用一个包的概念(我自己的术语)

GET /myApp/car/3/bundle
Run Code Online (Sandbox Code Playgroud)

哪会回来

"Bundle"
{

    "Car"
    {
    "url" : "/myApp/car/3",
    "id" : 3,
    "registration" : "DF01 1DXM",
    "mileage": 1000,
    "model" : "/myApp/carModel/54",       // where 54 maps back to the Id for the Toyota Prius
    },
    "CarModel"
    {
    "url" : "/myApp/carModel/54",
    "id" : 54,
    "manufacturer" : "Toyota",
    "model": "Prius,
    } // and so on and so forth for attributes
}
Run Code Online (Sandbox Code Playgroud)

然后解析结果以找到链接,减少抖动,同时仍保持完全平坦的图形

对于0 ..*列表,您可以使用ListContainer(我自己的术语)

"ListContainer"
{
     "url" : "/myApp/car/3/attribute" // this URL returns a list of attributes, you may decide to use 2 base URLs, I've used 1 base
     "ItemType": "CarModelAttribute",
     "Count": 2,
     "Items" : [
                {attribute JSON goes here}, {attribute JSON goes here}
     ]
}       
Run Code Online (Sandbox Code Playgroud)

始终返回200 for list获取URL格式正确的位置,但包含空列表

"ListContainer"
{
     "url" : "/myApp/car/3/attribute" // this URL returns a list of attributes, you may decide to use 2 base URLs, I've used 1 base
     "ItemType": "CarModelAttribute",
     "Count": 0,
     "Items" : [],
     "emptyReason" : "This Car Model has no attributes"
}      
Run Code Online (Sandbox Code Playgroud)

不要忘记使用好的状态代码,404找不到,410(我认为,检查维基百科)删除的实体等.使用适当的POST/PUT代码验证失败(不可处理的实体)

注意隔间的使用

/myApp/car/3/attribute
Run Code Online (Sandbox Code Playgroud)

所以它很容易阅读,对于Car id = 3,我想要它的所有属性(这会返回属性1,2,3,4)

因此,如果您希望作为快捷方式(如果ID当然是唯一的),您将来可以使用"/ myApp/attribute/4"

如果您希望发布整车,则发布捆绑包,所有ID都为空(除非您希望支持客户端ID生成(通常基于GUID,不要使用序列!).

如果您希望一次发布许多汽车,请使用交易(确保您的端点注释为交易),例如

POST /myApp/car/transaction
Run Code Online (Sandbox Code Playgroud)

其中一个Transaction资源看起来像一个bundle,但是有一个bundle列表

"transaction" 
{
        bundle : [ ]
}
Run Code Online (Sandbox Code Playgroud)

其中一个包将是汽车及其属性,而另一个可能是模型信息(因此,如果您想要10辆丰田普锐斯,则不要复制模型信息).

我希望有所帮助.

一些澄清:

"1.为什么有url作为Car-entity的第一属性?"

它通常被视为"良好实践",包括一个自我参考,你可以如何有意识地回到你拥有的资源.

想象一下,如果有人为你保存了JSON,你并不总是知道它来自何处,使用嵌入式URL,你做到了!

"2.如果是Bundle你写的:"然后解析结果找到链接,减少喋喋不休,同时仍然保持一个完全平坦的图形" - >如果一切都在那里我做什么与v链接?3.相关2:图表是平的.为什么没有"收容"?"

你有一个平面图,以避免圆形引用/ n深度对象.

想象一下,你一次又一次地在汽车上有相同的部分,我们会说轮子,它有20kb的属性.

您可以a)每次重现相同的20kb属性,或者b)将对象中的链接放到/ carPart/3中,然后在包中稍后将完整对象放置一次.

更多努力解析,但避免过度嵌套和重复.

此外,作为奖励,如果您不想要捆绑版本,则可以选择不包括子对象,因此您的资源只有链接,这非常轻量级.这样,如果客户想要更多信息,他们只需要获取它.

"4.这是ListContainer的常用做法吗?虽然它有点道理."

我见过的几个REST实现使用了类似的东西,这是一个包含类型信息的列表的好方法,并且能够告诉客户端列表可能为空的原因.

我的经验法则是,如果它是一个直接的ID引用,例如/ car/3,它将作为肯定的1匹配或状态代码返回(即您几乎可以肯定该项目存在).如果你"找到"某些东西,例如

/ car?manufacturer ="Toyota"&model ="Prius"那么你期望列表容器为0 ..*即使真的,它可能是0..1.

"5."端点注释为事务性"?事务和REST听起来很糟糕,你能解释一下.我理解不会复制模型信息."

事务很棘手,但即使在REST中,有时你也必须做一些原子事.这为你提供了这种能力.我不喜欢使用它,但有时这是不可避免的.