Chapter9:CREATING RESUOURCES

9 CREATING RESUOURCES

In this section, we are going to show you how to use the POST HTTP method to create resources in the database.
在本节中,我们将向您展示如何使用 POST HTTP 方法在数据库中创建资源

So, let’s start.
那么,让我们开始吧。

9.1 Handing POST Requests

Firstly, let’s modify the decoration attribute for the GetCompany action in the Companies controller:
首先,让我们修改Companies控制器中 GetCompany 操作的 decoration 属性

[HttpGet("{id}", Name = "CompanyById")]

With this modification, we are setting the name for the action. This name will come in handy in the action method for creating a new company.
通过此修改,我们将设置操作的名称。此名称将在创建新公司的操作方法中派上用场。

We have a DTO class for the output (the GET methods), but right now we need the one for the input as well. So, let’s create a new class in the Entities/DataTransferObjects folder:
我们有一个用于输出的DTO类(GET方法),但现在我们也需要一个用于输入的DTO类。因此,让我们在Entities/DataTransferObjects文件夹中创建一个新类:

namespace Entities.DataTransferObjects
{
    public class CompanyForCreationDto { 
        public string Name { get; set; } 
        public string Address { get; set; } 
        public string Country { get; set; } 
    }
}

We can see that this DTO class is almost the same as the Company class but without the Id property. We don’t need that property when we create an entity.
我们可以看到,此 DTO 类几乎与 Company类相同,但没有 Id 属性。创建实体时,我们不需要该属性。

We should pay attention to one more thing. In some projects, the input and output DTO classes are the same, but we still recommend separating them for easier maintenance and refactoring of our code. Furthermore, when we start talking about validation, we don’t want to validate the output objects — but we definitely want to validate the input ones.
我们应该注意另一件事。在某些项目中,输入和输出 DTO 类是相同的,但我们仍然建议将它们分开,以便更轻松地维护和重构代码。此外,当我们开始讨论验证时,我们不想验证输出对象 – 但我们肯定想要验证输入对象。

With all of that said and done, let’s continue by modifying the ICompanyRepository interface:
说完了这么多,让我们继续修改 ICompanyRepository 接口:

using Entities.Models;
using System;
using System.Collections.Generic;

namespace Contracts
{
    public interface ICompanyRepository {
        IEnumerable<Company> GetAllCompanies(bool trackChanges);
        Company GetCompany(Guid companyId, bool trackChanges);
        void CreateCompany(Company company);
    }
}

After the interface modification, we are going to implement that interface:
在接口修改之后,我们将实现该接口:

using Contracts;
using Entities.Models;
using Entities;
using System.Collections.Generic;
using System.Linq;
using System;

namespace Repository
{
    public class CompanyRepository : RepositoryBase<Company>, ICompanyRepository
    { 
        public CompanyRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }
        public IEnumerable<Company> GetAllCompanies(bool trackChanges) => 
            FindAll(trackChanges).OrderBy(c => c.Name).ToList();

        public Company GetCompany(Guid companyId, bool trackChanges) => 
            FindByCondition(c => c.Id.Equals(companyId), trackChanges).
            SingleOrDefault();

        public void CreateCompany(Company company) => Create(company);
    }
}

We don’t explicitly generate a new Id for our company; this would be done by EF Core. All we do is to set the state of the company to Added.
我们不会明确地为我们的公司生成新的ID;这将由EF Core完成。我们所要做的就是将公司的状态设置为“已添加”。

Before we add a new action in our Companies controller, we have to create another mapping rule for the Company and CompanyForCreationDto objects. Let’s do this in the MappingProfile class:
Companies控制器中添加新操作之前,我们必须为CompanyCompanyForCreationDto 对象创建另一个映射规则。让我们在 MappingProfile 类中执行此操作:

using AutoMapper;
using Entities.DataTransferObjects;
using Entities.Models;

namespace CompanyEmployees
{
    public class MappingProfile : Profile { 
        public MappingProfile() { 
            CreateMap<Company, CompanyDto>()
                .ForMember(c => c.FullAddress, 
                opt => opt.MapFrom(x => string.Join(' ', x.Address, x.Country)));

            CreateMap<Employee, EmployeeDto>();

            CreateMap<CompanyForCreationDto, Company>();
        } 
    }
}

Our POST action will accept a parameter of the type CompanyForCreationDto, but we need the Company object for creation. Therefore, we have to create this mapping rule.
我们的 POST 操作将接受 CompanyForCreationDto 类型的参数,但我们需要 Company 对象进行创建。因此,我们必须创建此映射规则。

Last, let’s modify the controller:
最后,让我们修改控制器:

[HttpPost]
        public IActionResult CreateCompany([FromBody] CompanyForCreationDto company)
        {
            if (company == null)
            {
                _logger.LogError("CompanyForCreationDto object sent from client is null.");
                return BadRequest("CompanyForCreationDto object is null");
            }
            var companyEntity = _mapper.Map<Company>(company);
            _repository.Company.CreateCompany(companyEntity);
            _repository.Save();
            var companyToReturn = _mapper.Map<CompanyDto>(companyEntity);
            return CreatedAtRoute("CompanyById", new { id = companyToReturn.Id }, companyToReturn);
        }

Let’s use Postman to send the request and examine the result:
让我们使用Postman发送请求并检查结果:
(在POSTMAN中选择POST方法,在BODY中选择RAW,然后选择JSON,在中间的文本区域输出一个公司的JSON内容信息)

https://localhost:5001/api/companies

alt

9.2 Code Explanation

Let’s talk a little bit about this code. The interface and the repository parts are pretty clear, so we won’t talk about that. But the code in the controller contains several things worth mentioning.
让我们来谈谈这个代码。界面和存储库部分非常清晰,因此我们不会讨论这一点。但是控制器中的代码包含几件值得一提的事情。

If you take a look at the request URI, you’ll see that we use the same one as for the GetCompanies action: api/companies — but this time we are using the POST request.
如果您查看请求 URI,您会发现我们使用与 GetCompanies 操作相同的请求 URI:api/companies — 但这次我们使用的是 POST 请求。

The CreateCompany method has its own [HttpPost] decoration attribute, which restricts it to POST requests. Furthermore, notice the company parameter which comes from the client. We are not collecting it from the URI but from the request body. Thus the usage of the [FromBody] attribute. Also, the company object is a complex type; therefore, we have to use [FromBody].
CreateCompany 方法具有自己的 [HttpPost] 修饰属性,该属性将其限制为 POST 请求。此外,请注意来自客户的公司参数。我们不是从 URI 收集它,而是从请求正文中收集它。因此,用法 [FromBody] 属性。此外,公司对象是复杂类型;因此,我们必须使用 [FromBody]

If we wanted to, we could explicitly mark the action to take this parameter from the URI by decorating it with the [FromUri] attribute, though we wouldn’t recommend that at all because of security reasons and the complexity of the request.
如果我们愿意,可以通过用 [FromUri] 属性修饰它来显式标记从 URI 获取此参数的操作,尽管由于安全原因和请求的复杂性,我们根本不建议这样做。

Because the company parameter comes from the client, it could happen that it can’t be deserialized. As a result, we would need to validate it against the reference type’s default value, which is null.
由于 company 参数来自客户端,因此可能会发生无法反序列化的情况。因此,我们需要根据引用类型的默认值(null)对其进行验证。

After validation, we map the company for creation to the company entity, call the repository method for creation, and call the Save() method to save the entity to the database. After the save action, we map the company entity to the company DTO object to return it to the client.
验证后,我们将要创建的公司映射到公司实体,调用存储库方法进行创建,并调用 Save() 方法将实体保存到数据库中。保存操作后,我们将公司实体映射到公司 DTO 对象,以将其返回给客户端。

The last thing to mention is this part of the code:
最后要提到的是代码的这一部分:

CreatedAtRoute("CompanyById", new { id = companyToReturn.Id }, companyToReturn);

CreatedAtRoute will return a status code 201, which stands for Created. Also, it will populate the body of the response with the new company object as well as the Location attribute within the response header with the address to retrieve that company. We need to provide the name of the action, where we can retrieve the created entity.
CreatedAtRoute 将返回状态代码 201,它代表 Created。此外,它还将使用新的公司对象以及具有用于检索该公司的地址的响应标头。我们需要提供操作的名称,我们可以在其中检索创建的实体。

If we take a look at the headers part of our response, we are going to see a link to retrieve the created company:
如果我们看一下响应的标头部分,我们将看到一个用于检索所创建公司的链接:

alt

Finally, from the previous example, we can confirm that the POST method is neither safe nor idempotent. We saw that when we send the POST request, it is going to create a new resource in the database — thus changing the resource representation. Furthermore, if we try to send this request a couple of times, we will get a new object for every request (it will have a different Id for sure).
最后,从前面的示例中,我们可以确认 POST 方法既不安全也不幂等。我们看到,当我们发送 POST 请求时,它将在数据库中创建一个新资源,从而更改资源表示形式。此外,如果我们尝试发送此请求几次,我们将为每个请求获取一个新对象(它肯定会有不同的Id)。

Let’s continue with child resources creation.
让我们继续创建子资源。

9.3 Creating a Child Resource

While creating our company, we created the DTO object required for the CreateCompany action. So, for employee creation, we are going to do the same thing:
在创建公司时,我们创建了“CreateCompany”操作所需的 DTO 对象。因此,对于员工创建,我们将做同样的事情:

namespace Entities.DataTransferObjects
{
    public class EmployeeForCreationDto { 
        public string Name { get; set; } 
        public int Age { get; set; } 
        public string Position { get; set; } }
}

We don’t have the Id property because we are going to create that Id on the server-side. But additionally, we don’t have the CompanyId because we accept that parameter through the route:
我们没有 Id 属性,因为我们将在服务器端创建该 Id。但除此之外,我们没有公司Id,因为我们通过路由接受该参数:

[Route("api/companies/{companyId}/employees")]

The next step is to modify the IEmployeeRepository interface:
下一步是修改 IEmployeeRepository 接口:

using Entities.Models;
using System.Collections.Generic;
using System;

namespace Contracts
{
    public interface IEmployeeRepository 
    {
        IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges);
        Employee GetEmployee(Guid companyId, Guid id, bool trackChanges);

        void CreateEmployeeForCompany(Guid companyId, Employee employee);
    }
}

Of course, we have to implement this interface:
当然,我们必须实现这个接口:

using Contracts;
using Entities.Models;
using Entities;
using System.Collections.Generic;
using System;
using System.Linq;

namespace Repository
{
    public class EmployeeRepository : RepositoryBase<Employee>, IEmployeeRepository { 
        public EmployeeRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }
        public IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges) => 
            FindByCondition(e => e.CompanyId.Equals(companyId), trackChanges).OrderBy(e => e.Name);

        public Employee GetEmployee(Guid companyId, Guid id, bool trackChanges) => 
            FindByCondition(e => e.CompanyId.Equals(companyId) && e.Id.Equals(id), trackChanges).SingleOrDefault();

        public void CreateEmployeeForCompany(Guid companyId, Employee employee) { 
            employee.CompanyId = companyId; 
            Create(employee); 
        }
    }
}

Because we are going to accept the employee DTO object in our action, but we also have to send an employee object to this repository method, we have to create an additional mapping rule in the MappingProfile class:
由于我们将在操作中接受员工 DTO 对象,但还必须将员工对象发送到此存储库方法,因此我们必须在 MappingProfile 类中创建其他映射规则:

using AutoMapper;
using Entities.DataTransferObjects;
using Entities.Models;

namespace CompanyEmployees
{
    public class MappingProfile : Profile { 
        public MappingProfile() { 
            CreateMap<Company, CompanyDto>()
                .ForMember(c => c.FullAddress, 
                opt => opt.MapFrom(x => string.Join(' ', x.Address, x.Country)));

            CreateMap<Employee, EmployeeDto>();

            CreateMap<CompanyForCreationDto, Company>();

            CreateMap<EmployeeForCreationDto, Employee>();
        } 
    }
}

Now, we can add a new action in the EmployeesController:
现在,我们可以在员工控制器中添加一个新操作:

[HttpPost]
        public IActionResult CreateEmployeeForCompany(Guid companyId, [FromBody] EmployeeForCreationDto employee)
        {
            if (employee == null)
            {
                _logger.LogError("EmployeeForCreationDto object sent from client is null.");
                return BadRequest("EmployeeForCreationDto object is null");
            }
            var company = _repository.Company.GetCompany(companyId, trackChanges: false);
            if (company == null)
            {
                _logger.LogInfo($"Company with id: {companyId} doesn't exist in the database.");
                return NotFound();
            }
            var employeeEntity = _mapper.Map<Employee>(employee);

            _repository.Employee.CreateEmployeeForCompany(companyId, employeeEntity);
            _repository.Save();
            var employeeToReturn = _mapper.Map<EmployeeDto>(employeeEntity);

            return CreatedAtRoute("GetEmployeeForCompany", new { companyId, id = employeeToReturn.Id }, employeeToReturn);
        }

There are some differences in this code compared to the CreateCompany action. The first is that we have to check whether that company exists in the database because there is no point in creating an employee for a company that does not exist.
CreateCompany 操作相比,此代码中存在一些差异。首先,我们必须检查该公司是否存在于数据库中,因为为不存在的公司创建员工是没有意义的。

The second difference is the return statement, which now has two parameters for the anonymous object.
第二个区别是 return 语句,它现在有两个匿名对象的参数。

For this to work, we have to modify the HTTP attribute above the GetEmployeeForCompany action:
为此,我们必须修改 GetEmployeeForCompany 操作上方的 HTTP 属性:

[HttpGet("{id}", Name = "GetEmployeeForCompany")]

Let’s give this a try:
让我们试一试:
https://localhost:5001/api/companies/53a1237a-3ed3-4462-b9f0-5a7bb1056a33/employees
(原文的GUID不在数据库中,请查看数据库中的GUID,替换上面URL中的即可)

alt

Excellent. A new employee was created.
非常好。创建了新员工。

If we take a look at the Headers tab, we’ll see a link to fetch our newly created employee. If you copy that link and send another request with it, you will get this employee for sure:
如果我们看一下“标题”选项卡,我们将看到一个链接,用于获取新创建的员工。如果您复制该链接并随之发送另一个请求,您肯定会得到以下员工:
alt

9.4 Creating Children Resources Together with a Parent

There are situations where we want to create a parent resource with its children. Rather than using multiple requests for every single child, we want to do this in the same request with the parent resource.
在某些情况下,我们希望创建具有其子资源的父资源。我们希望在对父资源执行同一请求时,而不是对每个子项使用多个请求。

We are going to show you how to do this.
我们将向您展示如何做到这一点。

The first thing we are going to do is extend the CompanyForCreationDto class:
我们要做的第一件事是将CompanyForCreationDto 扩展到类:

using System.Collections.Generic;

namespace Entities.DataTransferObjects
{
    public class CompanyForCreationDto { 
        public string Name { get; set; } 
        public string Address { get; set; } 
        public string Country { get; set; }

        public IEnumerable<EmployeeForCreationDto> Employees { get; set; }
    }
}

We are not going to change the action logic inside the controller nor the repository logic; everything is great there. That’s all. Let’s test it:
我们不会更改控制器内部的操作逻辑,也不会更改存储库逻辑;那里的一切都很棒。就这样。让我们来测试一下:

{
    "name":"Electronics Solutions Ltd",
    "address":"312 Deliver Street,F 234",
    "Country":"USA",
    "employees":[
        {
            "name":"Joan Done",
            "age":29,
            "position":"Manager"
        },
        {
            "name":"Martin Geil",
            "age":29,
            "position":"Administrative"
        }
    ]
}

https://localhost:5001/api/companies

alt

You can see that this company was created successfully.
您可以看到这家公司已成功创建。

Now we can copy the location link from the Headers tab, paste it in another Postman tab, and just add the /employees part:
现在,我们可以从“标题”选项卡复制位置链接,将其粘贴到另一个Postman选项卡中,然后只需添加 /employees 部分:

alt

We have confirmed that the employees were created as well.
我们已经确认员工也是创建的。

9.5 Creating a Collection of Resources

Until now, we have been creating a single resource whether it was Company or Employee. But it is quite normal to create a collection of resources, and in this section that is something we are going to work with.
到目前为止,我们一直在创建单个资源,无论是公司还是员工。但是,创建资源集合是很正常的,在本节中,我们将要使用。‌

If we take a look at the CreateCompany action, for example, we can see that the return part points to the CompanyById route (the GetCompany action). That said, we don’t have the GET action for the collection creating action to point to. So, before we start with the POST collection action, we are going to create the GetCompanyCollection action in the Companies controller.
例如,如果我们看一下 CreateCompany 操作,我们可以看到返回部分指向 CompanyById 路由(GetCompany 操作)。也就是说,我们没有集合创建操作指向的 GET 操作。因此,在开始 POST 收集操作之前,我们将在公司控制器中创建 GetCompanyCollection 操作。

But first, let’s modify the ICompanyRepository interface:
但首先,让我们修改 ICompanyRepository 接口:

using Entities.Models;
using System;
using System.Collections.Generic;

namespace Contracts
{
    public interface ICompanyRepository {
        IEnumerable<Company> GetAllCompanies(bool trackChanges);
        Company GetCompany(Guid companyId, bool trackChanges);
        void CreateCompany(Company company);

        IEnumerable<Company> GetByIds(IEnumerable<Guid> ids, bool trackChanges);
    }
}

Then we have to change the CompanyRepository class:
然后,我们必须更改 CompanyRepository 类:

using Contracts;
using Entities.Models;
using Entities;
using System.Collections.Generic;
using System.Linq;
using System;

namespace Repository
{
    public class CompanyRepository : RepositoryBase<Company>, ICompanyRepository
    { 
        public CompanyRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }
        public IEnumerable<Company> GetAllCompanies(bool trackChanges) => 
            FindAll(trackChanges).OrderBy(c => c.Name).ToList();

        public Company GetCompany(Guid companyId, bool trackChanges) => 
            FindByCondition(c => c.Id.Equals(companyId), trackChanges).
            SingleOrDefault();

        public void CreateCompany(Company company) => Create(company);

        public IEnumerable<Company> GetByIds(IEnumerable<Guid> ids, bool trackChanges) => 
            FindByCondition(x => ids.Contains(x.Id), trackChanges).ToList();
    }
}

After that, we can add a new action in the controller:
之后,我们可以在控制器中添加一个新操作:

[HttpGet("collection/({ids})", Name = "CompanyCollection")]
        public IActionResult GetCompanyCollection(IEnumerable<Guid> ids)
        {
            if (ids == null)
            {
                _logger.LogError("Parameter ids is null");
                return BadRequest("Parameter ids is null");
            }
            var companyEntities = _repository.Company.GetByIds(ids, trackChanges: false);
            if (ids.Count() != companyEntities.Count())
            {
                _logger.LogError("Some ids are not valid in a collection");
                return NotFound();
            }
            var companiesToReturn = _mapper.Map<IEnumerable<CompanyDto>>(companyEntities);
            return Ok(companiesToReturn);
        }

And that’s it. These actions are pretty straightforward, so let’s continue towards POST implementation:
就是这样。这些操作非常简单,因此让我们继续实现POST:

[HttpPost("collection")]
        public IActionResult CreateCompanyCollection([FromBody] IEnumerable<CompanyForCreationDto> companyCollection)
        {
            if (companyCollection == null)
            {
                _logger.LogError("Company collection sent from client is null.");
                return BadRequest("Company collection is null");
            }
            var companyEntities = _mapper.Map<IEnumerable<Company>>(companyCollection);
            foreach (var company in companyEntities)
            {
                _repository.Company.CreateCompany(company);
            }
            _repository.Save();
            var companyCollectionToReturn = _mapper.Map<IEnumerable<CompanyDto>>(companyEntities);
            var ids = string.Join(",", companyCollectionToReturn.Select(c => c.Id));
            return CreatedAtRoute("CompanyCollection", new { ids }, companyCollectionToReturn);
        }

So, we check if our collection is null and if it is, we return a bad request. If it isn’t, then we map that collection and save all the collection elements to the database. Finally, we take all the ids as a comma-separated string and navigate to the GET action for fetching our created companies.
因此,我们检查集合是否为空,如果为空,则返回错误请求。如果不是,则映射该集合并将所有集合元素保存到数据库中。最后,我们将所有 id 作为逗号分隔的字符串,并导航到 GET 操作以获取我们创建的公司。

Now you may ask, why are we sending a comma-separated string when we expect a collection of ids in the GetCompanyCollection action?
现在您可能会问,当我们期望在 GetCompanyCollection 操作中收集 id 时,为什么我们要发送逗号分隔的字符串?

Well, we can’t just pass a list of ids in the CreatedAtRoute method because there is no support for the Header Location creation with the list. You may try it, but we’re pretty sure you would get the location like this:
好吧,我们不能只在 CreatedAtRoute 方法中传递 id 列表,因为不支持使用列表创建标头位置。您可以尝试一下,但我们非常确定您会得到这样的位置:

alt

We can test our create action now:
我们现在可以测试我们的创建操作:
https://localhost:5001/api/companies/collection

[
    {
        "name":"Sales all over the world Ltd",
        "address":"355 Open Street ,B 784",
        "country":"USA"
    },
    {
        "name":"Branding Ltd",
        "address":"255 Main street,K 334",
        "country":"USA"
    }
]

alt

Excellent. Let’s check the header tab:
非常好。让我们检查header选项卡:

alt

We can see a valid location link. So, we can copy it and try to fetch our newly created companies:
我们可以看到一个有效的位置链接。因此,我们可以复制它并尝试获取我们新创建的公司:

alt

But we are getting the 415 Unsupported Media Type message. This is because our API can’t bind the string type parameter to the IEnumerable argument.
但是我们收到“ 415 不支持的媒体类型 ”消息。这是因为我们的 API 无法将字符串类型参数绑定到 IEnumerable 参数。

Well, we can solve this with a custom model binding.
好吧,我们可以通过自定义模型绑定来解决此问题。

9.6 Model Binding in API

Let’s create the new folder ModelBinders in the main project and inside the new class ArrayModelBinder:
在主项目中创建文件夹 ModelBinders ,添加一个新类 ArrayModelBinder

using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.ComponentModel;
using System.Reflection;
using System;
using System.Threading.Tasks;
using System.Linq;

namespace CompanyEmployees.ModelBinders
{
    public class ArrayModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (!bindingContext.ModelMetadata.IsEnumerableType)
            {
                bindingContext.Result = ModelBindingResult.Failed();
                return Task.CompletedTask;
            }
            var providedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
            if (string.IsNullOrEmpty(providedValue))
            {
                bindingContext.Result = ModelBindingResult.Success(null);
                return Task.CompletedTask;
            }
            var genericType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
            var converter = TypeDescriptor.GetConverter(genericType);
            var objectArray = providedValue.Split(new[] { "," },
                StringSplitOptions.RemoveEmptyEntries).
                Select(x => converter.ConvertFromString(x.Trim())).
                ToArray();
            var guidArray = Array.CreateInstance(genericType, objectArray.Length);
            objectArray.CopyTo(guidArray, 0); bindingContext.Model = guidArray;
            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
            return Task.CompletedTask;
        }
    }
}

At first glance, this code might be hard to comprehend, but once we explain it, it will be easier to understand.
乍一看,这段代码可能很难理解,但一旦我们解释它,它将更容易理解。

We are creating a model binder for the IEnumerable type. Therefore, we have to check if our parameter is the same type.
我们正在为 IEnumerable 类型创建一个模型绑定器。因此,我们必须检查我们的参数是否为同一类型。

Next, we extract the value (a comma-separated string of GUIDs) with the ValueProvider.GetValue() expression. Because it is type string, we just check whether it is null or empty. If it is, we return null as a result because we have a null check in our action in the controller. If it is not, we move on.
接下来,我们使用 ValueProvider.GetValue() 表达式提取值(以逗号分隔的 GUID 字符串)。因为它是字符串类型,所以我们只检查它是空还是空。如果是,则返回 null 作为结果,因为我们在控制器的操作中有一个 null 检查。如果不是,我们继续前进。

In the genericType variable, with the reflection help, we store the type the IEnumerable consists of. In our case, it is GUID. With the converter variable, we create a converter to a GUID type. As you can see, we didn’t just force the GUID type in this model binder; instead, we inspected what is the nested type of the IEnumerable parameter and then created a converter for that exact type, thus making this binder generic.
genericType 变量中,通过反射帮助,我们存储 IEnumerable 包含的类型。在我们的例子中,它是GUID。随着转换器变量,我们创建一个 GUID 类型的转换器。如您所见,我们不只是强制此模型绑定器中的GUID类型;相反,我们检查了 IEnumerable 参数的嵌套类型是什么,然后为该确切类型创建了一个转换器,从而使此绑定器成为通用的。

After that, we create an array of type object (objectArray) that consist of all the GUID values we sent to the API and then create an array of GUID types ( guidArray ), copy all the values from the objectArray to the guidArray, and assign it to the bindingContext.
之后,我们创建一个类型对象( objectArray )数组,其中包含我们发送到API的所有GUID值,然后创建一个GUID类型数组( guidArray ),将所有值从objectArray 复制到 guidArray ,并将其分配给绑定 bindingContext

And that is it. Now, we have just to make a slight modification in the GetCompanyCollection action:
就是这样。现在,我们只需要对 GetCompanyCollection 操作:

public IActionResult GetCompanyCollection([ModelBinder(BinderType = typeof(ArrayModelBinder))]IEnumerable<Guid> ids)

Excellent.
非常好。

Our ArrayModelBinder will be triggered before an action executes. It will convert the sent string parameter to the IEnumerable type, and then the action will be executed:
我们的 ArrayModelBinder 将在操作执行之前触发。它将发送的字符串参数转换为 IEnumerable类型,然后执行操作:

alt

Well done.
干的好。

We are ready to continue towards DELETE actions.
我们已准备好继续执行 DELETE 操作。

发表评论