本文转自:http://www.drdobbs.com/windows/using-odata-from-aspnet/240168672

By Gastón Hillar, July 01, 2014

Post a Comment

Take advantage of your knowledge of both ASP.NET MVC and the ASP.NET Web API to easily create OData endpoints.

OData (Open Data Protocol) is a standardized protocol for creating and consuming data APIs through regular HTTP requests and REST. It is the “ODBC of the Web” and provides a standard solution for very common Web API patterns. For example, OData includes support for queries, paging, and batching, among many other typical requirements.

Creating an OData Endpoint

Microsoft ASP.NET Web API OData adds most of the things necessary to create OData endpoints and easily provide OData query syntax using the ASP.NET Web API (initially introduced in .NET Framework 4.5). ASP.NET Web API 2.2 added many improvements that make it easier to work with OData, including support for OData 4.0. In this article, I provide an example that uses the features provided in Microsoft ASP.NET Web API 2.2 OData.

Creating an OData Endpoint

OData is a standardized protocol for creating and consuming data APIs through regular HTTP requests and REST. One of the most interesting things about OData is that it provides a standard solution for very common Web API patterns. For example, OData includes support for queries, paging, and batching, among many other typical requirements. ASP.NET Web API OData provides many components to make it easy to implement OData services.

ASP.NET Web API 2 was included in Visual Studio 2013 and provided support for OData 3.0. The recently launched ASP.NET Web API 2.2 added support for the newest version of the standardized protocol: OData 4.0. In addition, the newest ASP.NET Web API version includes many interesting improvements related to OData, such as attribute routing, model aliasing, support for Enums, ETags, the $format query option, and the ability to specify which properties can be sorted, expanded, filtered, or navigated.

I want to work with ASP.NET Web API 2.2 in Visual Studio 2013, so I have to run a few NuGet Package Manager Console commands. I'll explain the steps to create a project that generates an OData endpoint and allows you to run OData queries on a simple Games entity with support provided by Entity Framework, but you don't have to use Entity Framework to take advantage of ASP.NET Web API OData. You can use a similar structure with other data sources.

Create a new ASP.NET Web Application project in Visual Studio 2013. I'll use Games for both the project and solution names.

Select the Empty template and check the Web API checkbox in the New ASP.NET Project dialog box. Then, click OK. Visual Studio 2013 will add the folders and core references for the ASP.NET Web API.

Execute the following commands in the NuGet Package Manager Console to access the latest ASP.NET Web API 2.2. The package follows the Semantic Versioning specification, so it has version 5.2.0. After executing this command, the project will be working with ASP.NET Web API 2.2:

1
2
Install-Package Microsoft.AspNet.WebApi -Version 5.2.0
Install-Package Microsoft.AspNet.WebApi.OData

I'll use a simple Game entity model to represent the data for the OData service. Add a Gameclass to the Models folder. The following lines show the code for this new class. Notice that I use an int for the key (GameId). I don't use a Guid to keep the HTTP requests easier to understand.

1
2
3
4
5
6
7
8
9
10
namespace Games.Models
{
    public class Game
    {
        public int GameId { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public int ReleaseYear { get; set; }
    }
}

Now, it is necessary to build the project because I can take advantage of the Visual Studio 2013 scaffolding, which will use reflection to gather information about the Game class.

I want to add a Web API 2 OData controller with CRUD actions to allow me to create, read, update, delete, and list entities from an Entity Framework data context. Visual Studio 2013 will generate the class that handles HTTP requests to perform these actions. Right-click on the Controllers folder within Solution Explorer and select Add | Controller…. Select Web API 2 OData Controller with actions, using Entity Framework, and click Add.  Enter GamesControllerfor the controller name and check the Use Async Controller Actions checkbox in the Add Controller dialog box. Select Game (Games.Models) in the Model class dropdown list, and click on the New Data Context… button. Visual Studio will specify Games.Models.GamesContext as the default new data context type name in a new dialog box. Leave the default name and click Add. Finally, click Add when you are back in the Add Controller dialog box.

Visual Studio will create the following GamesContext class in Models/GamesContext.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System.Data.Entity;
 
namespace Games.Models
{
    public class GamesContext : DbContext
    {
        public GamesContext() : base("name=GamesContext")
        {
        }
 
        public System.Data.Entity.DbSet<Games.Models.Game> Games { get; set; }
     
    }
}

The following lines show the generated code for the GamesController class in Controllers/GamesController.csGamesController implements the abstract class System.Web.Http.OData.ODataControllerODataController is the base class for OData controllers that supports writing and reading data using the OData formats. I've replaced the deprecated [Queryable] attribute with [EnableQuery]. Sadly, the scaffolding still works with the previous ASP.NET Web API OData version and generates code that includes the deprecated attribute. In fact, in some cases, you might have problems with scaffolding and the newest ASP.NET Web API OData-related libraries. If you notice mixed versions in the packages, you can copy and paste the code to your project instead of following the steps I mentioned before.

The [EnableQuery] attribute enables a controller action to support OData query parameters. The controller uses the async modifier to define asynchronous methods for the POSTPUTPATCH/MERGE, and DELETE verbs. In addition, you will notice the use of the await keyword and many calls to methods that end with the Async suffix. Microsoft .NET Framework 4.5 introduced asynchronous methods. If you haven't worked with the async modified in ASP.NET MVC controllers, you can read Using Asynchronous Methods in ASP.NET 4.5 and in MVC 4 to get a feel for it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.OData;
using System.Web.Http.OData.Routing;
using Games.Models;
 
namespace Games.Controllers
{
    public class GamesController : ODataController
    {
        private GamesContext db = new GamesContext();
 
        // GET odata/Games
        [EnableQuery]
        public IQueryable<Game> GetGames()
        {
            return db.Games;
        }
 
        // GET odata/Games(5)
        [EnableQuery]
        public SingleResult<Game> GetGame([FromODataUri] int key)
        {
            return SingleResult.Create(db.Games.Where(game => game.GameId == key));
        }
 
        // PUT odata/Games(5)
        public async Task<IHttpActionResult> Put([FromODataUri] int key, Game game)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
 
            if (key != game.GameId)
            {
                return BadRequest();
            }
 
            db.Entry(game).State = EntityState.Modified;
 
            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!GameExists(key))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
 
            return Updated(game);
        }
 
        // POST odata/Games
        public async Task<IHttpActionResult> Post(Game game)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
 
            db.Games.Add(game);
            await db.SaveChangesAsync();
 
            return Created(game);
        }
 
        // PATCH odata/Games(5)
        [AcceptVerbs("PATCH", "MERGE")]
        public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Game> patch)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
 
            Game game = await db.Games.FindAsync(key);
            if (game == null)
            {
                return NotFound();
            }
 
            patch.Patch(game);
 
            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!GameExists(key))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
 
            return Updated(game);
        }
 
        // DELETE odata/Games(5)
        public async Task<IHttpActionResult> Delete([FromODataUri] int key)
        {
            Game game = await db.Games.FindAsync(key);
            if (game == null)
            {
                return NotFound();
            }
 
            db.Games.Remove(game);
            await db.SaveChangesAsync();
 
            return StatusCode(HttpStatusCode.NoContent);
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
 
        private bool GameExists(int key)
        {
            return db.Games.Count(e => e.GameId == key) > 0;
        }
    }
}

Notice the use of the System.Web.Http.AcceptVerbs attribute in the Patch method to make the action method respond to both the PATCH and MERGE HTTP verbs.

1
2
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Game> patch)

The parameterless GetGames method returns the entire Games collection. The GetGame method with a key parameter looks up a Game by its key (the GameId property). Both methods respond to HTTP GET; and the final method call will depend on the OData query. I'll discuss the effects of the [EnableQuery] attribute later.

The rest of the supported HTTP verbs use a single method for each verb: PUT (Put), POST(Post), and DELETE (Delete). Thus, you can query the entity set (GET), add a new game to the entity set (POST), perform a partial update to a game (PATCH/MERGE), replace an entire game (PUT), and delete a game (DELETE).

Take advantage of your knowledge of both ASP.NET MVC and the ASP.NET Web API to easily create OData endpoints.

The generated controller code includes commented code that allows you to add a route for the controller. Change the code in App_Start/WebApiConfig.cs with the following lines that create an entity data model, also known as EDM, for the OData endpoint and add a route to this OData endpoint.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.Web.Http;
using System.Web.Http.OData.Builder;
using Games.Models;
 
namespace Games
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Game>("Games");
            config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
        }
    }
}

The System.Web.Http.OData.Builder.ODataConventionModelBuilder class allows you to automatically map CLR classes to an EDM model based on a set of default naming conventions. You can have more control over the generating process by calling ODataModelBuilder methods to create the EDM. The call to the EntitySet method registers an entity set as part of the model. In this case, the name of the entity set is specified as Games in the single argument, and the conventions assume the controller is named GamesController. You can make additional calls to EntitySet to create the EMD model for each entity set you want to make available in a single endpoint.

Finally, the call to config.Routes.MapODataRoute maps the specified OData route and adds it to the OData endpoint. The first parameter specifies a friendly name for the route, and the second parameter sets the URI prefix for the endpoint. Thus, the URI for the Games entity set is /odata/Games. Obviously, it is necessary to add the hostname and port to the URI. For example, if the project URL is http://localhost:50723/, the URI for the Games entity set will be http://localhost:50723/odata/Games. The third parameter is the pre-built Microsoft.Data.Edm.IEdmModel that is retrieved from the builder with a call to the GetEdmModelmethod. In order to check the project URL, right-click on the project name (Games) in Solution Explorer, select Properties, click on Web, and read the configuration in the Project URL textbox.

Working with the OData Endpoint

You can press F5 to start debugging, and the OData endpoint will be ready to process requests. You can use either Telerik Fiddler or the curl utility to compose and send requests to the endpoint with different headers and check the results returned by the OData endpoint. Telerik Fiddler is a free Web debugging proxy with a GUI. The curl utility is a free and open source command line tool to transfer data to/from a server and it supports a large number of protocols. You can easily install curl in any Windows version from the Cygwin package installation option, and execute it from the Cygwin Terminal. I'll provide examples for both Fiddler and curl. I'll always use http://localhost:50723/ as my project URL in the samples (don't forget to replace it with your project URL in your own code).

If you send an HTTP GET request to http://localhost:50723/odata, you will receive the service document with the list of entity sets for the OData endpoint. In this case, the only entity set is Games. You can execute the following line in a Cygwin terminal to retrieve the service document with curl.

The following lines show the raw HTTP response:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
  <workspace>
    <atom:title type="text">Default</atom:title>
    <collection href="Games">
      <atom:title type="text">Games</atom:title>
    </collection>
  </workspace>
</service>

In Fiddler, click Composer or press F9, select GET in the dropdown menu in the Parsed tab, and enter http://localhost:50723/odata/ in the textbox at the right-hand side of the dropdown (see Figure 1). Then, click Execute and double click on the 200 result that appears on the capture log. If you want to see the raw response, just click on the Raw button below the Request Headers panel (see Figure 2).


Figure 1: Composing a request in Fiddler.


Figure 2: Reading the raw response for the request in Fiddler.

If you add $metadata to the previous URI, the OData endpoint will return the service metadata document that describes the data model of the service with the Conceptual Schema Definition Language (CSDL) XML language. You can execute the following line in a Cygwin terminal to retrieve the service metadata document with curl. In Fiddler, you don't need to add a backslash (\) before the dollar sign ($); you can enter the raw URI: http://localhost:50723/odata/$metadata.

1
curl -X GET "http://localhost:50723/odata/\$metadata"

The following lines show the raw HTTP response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
  <edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <Schema Namespace="Games.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityType Name="Game">
        <Key>
          <PropertyRef Name="GameId" />
        </Key>
        <Property Name="GameId" Type="Edm.Int32" Nullable="false" />
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Category" Type="Edm.String" />
        <Property Name="ReleaseYear" Type="Edm.Int32" Nullable="false" />
      </EntityType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
        <EntitySet Name="Games" EntityType="Games.Models.Game" />
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Establish a breakpoint at the first line of the Post method within the GamesController class to see all the work done under the hood by ASP.NET Web API OData. Then, compose and execute an HTTP POST request to http://localhost:50723/odata/Games in Fiddler with the following values in the request headers and request body:

  • Request headers: Content-Type: application/json
  • Request body: { "Name":"Tetris 2014","Category":"Puzzle","ReleaseYear":2014 }

You can achieve the same effect with the following curl command:

1
curl -H "Content-Type: application/json" -d '{ "Name":"Tetris 2013","Category":"Puzzle","ReleaseYear":2013 }' -X POST "http://localhost:50723/odata/Games"

The request specifies that the body will use JSON and the Post method in C# receives a Gameinstance. This method works the same way as an ASP.NET MVC controller Post method, in which you check the ModelState.IsValid property value.

1
public async Task<IHttpActionResult> Post(Game game)

In this case, the model is valid, and when you continue with the execution, either Fiddler or curl will display the following HTTP 201 Created response with the URI for the recently generated element: http://localhost:50723/odata/Games(1):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HTTP/1.1 201 Created
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?YzpcdXNlcnNcZ2FzdG9uXGRvY3VtZW50c1x2aXN1YWwgc3R1ZGlvIDIwMTNcUHJvamVjdHNcR2FtZXNcR2FtZXNcb2RhdGFcR2FtZXM=?=
X-Powered-By: ASP.NET
Date: Sat, 12 Jul 2014 04:09:44 GMT
Content-Length: 151
 
{
  "odata.metadata":"http://localhost:50723/odata/$metadata#Games/@Element","GameId":1,"Name":"Tetris 2014","Category":"Puzzle","ReleaseYear":2014
}

If you send an HTTP GET request to http://localhost:50723/odata/Games(1), you will receive the details for the recently saved element in the default JSON light serialization format. OData 3.0 introduced the JSON light serialization format to reduce footprint. The following two curl commands will produce the same result and will end in a call to the GetGame method with the key parameter that retrieves the Game from the database based on the received key value. (In Fiddler, you just need to add Accept: application/json to Request headers.)

1
2
curl -H "Accept: application/json" -X GET "http://localhost:50723/odata/Games(5)"

With or without the Accept: application/json line added to the request headers, the result will use the JSON light serialization format:

1
2
3
{
  "odata.metadata":"http://localhost:50723/odata/$metadata#Games/@Element","GameId":1,"Name":"Tetris 2014","Category":"Puzzle","ReleaseYear":2014
}

If you want to receive the response with the older OData v2 JSON "verbose" serialization format, you just need to add the Accept: application/json;odata=verbose line to the request headers. For example, the following curl command retrieves the same game with the verbose JSON format:

1
url -H "Accept: application/json;odata=verbose" -X GET "http://localhost:50723/odata/Games(1)"

The third option is to use the Atom Pub XML serialization format. If you want this format, you just need to add the Accept: application/atom+xml line to the request headers. The following curl command retrieves the same game with the Atom Pub XML format:

1
curl -H "Accept: application/json;odata=verbose" -X GET "http://localhost:50723/odata/Games(1)"

The GetGame method is decorated with the [EnableQuery] attribute; hence, you can use OData query parameters. For example, the following HTTP GET request uses the $select option to specify that it just wants the Name property to be included in the response body: http://localhost:50723/odata/Games(1)?$select=Name. In curl, you need to add a backslash (\) before the dollar sign ($):

1
curl -X GET "http://localhost:50723/odata/Games(1)?\$select=Name"

The following lines show the raw HTTP response that only includes the requested property value:

1
2
3
{
  "odata.metadata":"http://localhost:50723/odata/$metadata#Games/@Element&$select=Name","Name":"Tetris 2014"
}

The GetGames method is also decorated with the [EnableQuery] attribute. Thus, you can use the $filter query option to select the games that satisfy a specific predicate expression. For example, the following HTTP GET request uses the $filter option with the eq (equal) operator to retrieve the game whose name is equal to Tetris 2014:

1
http://localhost:50723/odata/Games?$filter=Name%20eq%20'Tetris%202014'

In curl, you need to add a backslash (\) before the dollar sign ($):

1
curl -X GET "http://localhost:50723/odata/Games?\$filter=Name%20eq%20'Tetris%202014'"

The following lines show the raw HTTP response:

1
2
3
4
5
6
7
{
  "odata.metadata":"http://localhost:50723/odata/$metadata#Games","value":[
    {
      "GameId":1,"Name":"Tetris 2014","Category":"Puzzle","ReleaseYear":2014
    }
  ]
}

The code for the GetGames method simply includes the [EnableQuery] attribute and is just one line that returns an IQueryable<Game>. As you can see, ASP.NET Web API OData performs a lot of work under the hood to parse and translate the OData query options and operators to a Linqquery.

Conclusion

As you have learned from this simple example, ASP.NET Web API OData allows you to take advantage of your existing knowledge of both APS.NET MVC and the ASP.NET Web API to easily create OData endpoints. The good news is that you can customize all the components and easily add features, such as complex server-side behavior modification via the addition of OData actions. Enjoy!


Gastón Hillar is a senior contributing editor at Dr. Dobb's.

[转]Using OData from ASP.NET的更多相关文章

  1. AngularJS使用OData请求ASP.NET Web API资源的思路

    本篇整理AngularJS使用OData请求ASP.NET Web API资源的思路. 首先给ASP.NET Web API插上OData的翅膀,通过NuGet安装OData. 然后,给control ...

  2. OData查询ASP.NET Web API全攻略

    本篇使用ASP.NET Web API来体验OData各种query. 首先是本篇即将用到的Model.使用的OData版本是4.0. public class Customer { public i ...

  3. OData – the best way to REST–实例讲解ASP.NET WebAPI OData (V4) Service & Client

    一.概念介绍 1.1,什么是OData? 还是看OData官网的简单说明: An open protocol to allow the creation and consumption of quer ...

  4. 对一个前端AngularJS,后端OData,ASP.NET Web API案例的理解

    依然chsakell,他写了一篇前端AngularJS,后端OData,ASP.NET Web API的Demo,关于OData在ASP.NET Web API中的正删改查没有什么特别之处,但在前端调 ...

  5. [转]ASP.NET Web API对OData的支持

    http://www.cnblogs.com/shanyou/archive/2013/06/11/3131583.html 在SOA的世界中,最重要的一个概念就是契约(contract).在云计算的 ...

  6. [转]OData – the best way to REST–实例讲解ASP.NET WebAPI OData (V4) Service & Client

    本文转自:http://www.cnblogs.com/bluedoctor/p/4384659.html 一.概念介绍 1.1,什么是OData? 还是看OData官网的简单说明: An open ...

  7. [水煮 ASP.NET Web API2 方法论](12-4)OData 支持的 Function 和 Action

    问题 在 Web API 中使用 OData Function 和 Action. 解决方案 可以通过 ODataModelBuilder,使用 OData 构建 ASP.NET Web API, E ...

  8. [水煮 ASP.NET Web API 2 方法论] 目 录

    一.ASP.NET 中的 Web API [水煮 ASP.NET Web API2 方法论](1-1)在MVC 应用程序中添加 ASP.NET Web API 与 ASP.NET MVC 在同一个进程 ...

  9. webAPi OData的使用

    一.OData介绍 开放数据协议(Open Data Protocol,缩写OData)是一种描述如何创建和访问Restful服务的OASIS标准. 二.OData 在asp.net mvc中的用法 ...

随机推荐

  1. 读取二元组列表,打印目录的层级结构-----C++算法实现

    要求是--某个文件中存储了一个最多3层的层级结构,其中每个元素都是一个自然数,它的存储方法是一个二元组的列表,每个二元组的形式为:(元素,父元素).现在希望能够通过读取该二元组列表,打印出目录的层级结 ...

  2. 云课堂Android模块化实战--如何设计一个通用性的模块

    本文来自 网易云社区 . 如何设计一个通用性的模块 前言 每个开发者都会知道,随着项目的开发,会发现业务在不断壮大,产品线越来越丰富,而留给开发的时间却一直有限,在有限的时间,尽快完成某个功能的迭代. ...

  3. 「AH2017/HNOI2017」礼物

    题目链接 戳我 \(Solution\) 应为我们可以将任意一个数列加上一个非负整数,即可以变为将一个数列加上一个整数(可以为负),我们将这个整数设为\(z\).所以要求的式子的变为: \[\sum_ ...

  4. 三,PHP中错误日志display_errors与error_reporting配置

    1,display_errors display_errors 错误回显,一般常用语开发模式,但是很多应用在正式环境中也忘记了关闭此选项.错误回显可以暴露出非常多的敏感信息,为攻击者下一步攻击提供便利 ...

  5. win10安装express遇到的问题。

    昨天在centos上成功安装了express,今天想在win10上面装一个,死活安装不了 express可以正常安装,但是每次安装express-generator的时候一直报错 659 silly ...

  6. 深度学习(tensorflow) —— 自己数据集读取opencv

    先来看一下我们的目录: dataset1 和creat_dataset.py 属于同一目录 mergeImg1 和mergeImg2 为Dataset1的两子目录(两类为例子)目录中存储图像等文件 核 ...

  7. loj #6570. 毛毛虫计数

    $ \color{#0066ff}{ 题目描述 }$ hsezoi 巨佬 olinr 喜欢 van 毛毛虫,他定义毛毛虫是一棵树,满足树上存在一条树链,使得树上所有点到这条树链的距离最多为 1. 给定 ...

  8. leetcode-575-Distribute Candies(计算一个数组中元素的种类的快速方法)

    题目描述: Given an integer array with even length, where different numbers in this array represent diffe ...

  9. [软件工程]项目选择与NABCD模型分析

    项目 内容 这个作业属于哪个课程 2019春季计算机学院软件工程(罗杰) 这个作业的要求在哪里 团队项目选择 这课程的目标是 以实践形式熟悉软件开发流程,团队开发,合作学习 本次作业对课程的帮助是 确 ...

  10. Laravel 控制器 Controller 传值到 视图 View 的几种方法总结

    单个值的传递   with public function index() { $test = "测试"; return view('test.index')->with(' ...