Không thể tuần tự hóa phản hồi trong API Web với Json


109

Tôi đang làm việc với ASP.NET MVC 5 Web Api. Tôi muốn tham khảo ý kiến ​​của tất cả người dùng của tôi.

Tôi đã viết api/usersvà tôi nhận được điều này:

"Loại 'ObjectContent`1' không thể tuần tự hóa phần thân phản hồi cho loại nội dung 'application / json; charset = utf-8'"

Trong WebApiConfig, tôi đã thêm những dòng này:

HttpConfiguration config = new HttpConfiguration();
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; 

Nhưng nó vẫn không hoạt động.

Chức năng của tôi cho dữ liệu trả về là:

public IEnumerable<User> GetAll()
{
    using (Database db = new Database())
    {
        return db.Users.ToList();
    }
}

Đối tượng giá trị trông như thế nào mà bạn đang cố gắng chuyển đến người tiêu dùng?
mckeejm

Cám ơn rất nhiều! Chỉ cần fyi - Tôi nghĩ rằng nên đọc: using (Database db = new Database ()) {List <UserModel> listOfUsers = new List <UserModel> (); foreach (var user in db.Users) {UserModel userModel = new UserModel (); userModel.FirstName = user.FirstName; userModel.LastName = user.LastName; listOfUsers.Add (userModel); } IEnumerable <UserModel> users = listOfUsers; trả lại người dùng; } .. vì tất cả các kết quả đều trả về các giá trị giống nhau.
Jared Whittington,

Câu trả lời:


76

Khi nói đến việc trả lại dữ liệu cho người tiêu dùng từ Web Api (hoặc bất kỳ dịch vụ web nào khác cho vấn đề đó), tôi thực sự khuyên bạn không nên chuyển lại các thực thể đến từ cơ sở dữ liệu. Sẽ đáng tin cậy và dễ bảo trì hơn nhiều khi sử dụng Mô hình trong đó bạn có quyền kiểm soát dữ liệu trông như thế nào chứ không phải cơ sở dữ liệu. Bằng cách đó, bạn không phải loay hoay với các bộ định dạng trong WebApiConfig. Bạn chỉ có thể tạo một UserModel có các Mô hình con làm thuộc tính và loại bỏ các vòng tham chiếu trong các đối tượng trả về. Điều đó làm cho người nối tiếp vui hơn nhiều.

Ngoài ra, thông thường không cần thiết phải xóa bộ định dạng hoặc các loại phương tiện được hỗ trợ nếu bạn chỉ chỉ định tiêu đề "Chấp nhận" trong yêu cầu. Việc nghịch ngợm những thứ đó đôi khi có thể khiến mọi thứ trở nên khó hiểu hơn.

Thí dụ:

public class UserModel {
    public string Name {get;set;}
    public string Age {get;set;}
    // Other properties here that do not reference another UserModel class.
}

Khi bạn tham khảo Mô hình, bạn muốn nói tôi đang làm gì? Trả về Số lượng Người dùng là Người mẫu.
CampDev

5
Bạn đang trả lại một Thực thể. Một thực thể tham chiếu đến một đối tượng trong DB có thể được truy xuất bởi một id duy nhất. Bạn đang trả lại tất cả các thực thể Người dùng từ cơ sở dữ liệu của mình. Tôi khuyên bạn nên tạo một lớp mới có tên "UserModel" và đối với mỗi thực thể Người dùng mà bạn nhận được từ cơ sở dữ liệu, hãy tạo một phiên bản mới của lớp mô hình dữ liệu được điền với thông tin cần thiết mà bạn muốn hiển thị. Trả về IEnumerable của các đối tượng UserModel trái ngược với các thực thể Người dùng. Đảm bảo rằng Thuộc tính mô hình không tham chiếu đến các phiên bản của lớp UserModel. Đó là những gì đang đưa bạn vào vấn đề này.
jensendp

3
ncampuzano đúng, bạn không muốn trả lại các thực thể được tạo tự động. Nếu bạn đang sử dụng các thủ tục được lưu trữ để truy cập cơ sở dữ liệu, sự phân tách sẽ rõ ràng hơn. Bạn sẽ cần phải tạo một đối tượng giá trị C # và các giá trị được ánh xạ từ IDataReader đến đối tượng giá trị. Vì bạn đang sử dụng EF, nên có các lớp được tạo cho bạn, nhưng đó là các lớp EF đặc biệt biết nhiều hơn các đối tượng giá trị. Bạn chỉ nên trả lại các đối tượng giá trị "câm" cho khách hàng của mình.
mckeejm

1
@Donny Nếu bạn đang sử dụng DBContext hoặc Kho lưu trữ trong bộ điều khiển của bạn đang trả lại các thực thể từ DB, thì bạn chỉ có thể ánh xạ các đối tượng tới các mô hình (DTO chẳng hạn) trong bộ điều khiển ... nhưng tôi thích có bộ điều khiển gọi một dịch vụ trả về mô hình / DTO. Kiểm tra AutoMapper - công cụ tuyệt vời để xử lý ánh xạ.
ben

1
@NH. Bạn hoàn toàn có thể sử dụng những trò tai quái nói trên, nhưng mọi thứ đều có nơi có chốn. Các "thực thể" được cung cấp bởi quyền truy cập vào Lớp dữ liệu thường phải ở trong Lớp dữ liệu. Bất kỳ thứ gì muốn sử dụng dữ liệu này trong Lớp nghiệp vụ của ứng dụng thường cũng sẽ sử dụng các "thực thể" ở dạng được chuyển đổi (Đối tượng miền). Và sau đó dữ liệu được trả về và đầu vào từ người dùng thường cũng sẽ là một dạng khác (Mô hình). Đồng ý rằng có thể rất tẻ nhạt khi thực hiện kiểu chuyển đổi này ở mọi nơi, nhưng đó là lúc các công cụ như AutoMapper thực sự hữu ích.
jensendp

147

Nếu bạn đang làm việc với EF, ngoài việc thêm mã bên dưới vào Global.asax

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
    .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters
    .Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);          

Đừng quên nhập

using System.Data.Entity;

Sau đó, bạn có thể trả lại các Mô hình EF của riêng mình

Đơn giản như vậy!


Ngay cả khi nó có thể hữu ích cho EF, giải pháp này không dành riêng cho EF và cũng hoạt động với các loại mô hình khác. Việc sử dụng dường như không cần thiết trong Global.asax. Nó có dành cho bộ điều khiển không?
Matthieu,

16
Một số giải thích về những gì mã này làm và ý nghĩa của nó sẽ được hoan nghênh.
Jacob

1
Cảm ơn tôi đã gặp phải vấn đề tương tự, câu trả lời này đã giúp tôi giải quyết vấn đề.
RK_Aus

Nó làm việc cho tôi. Không cần thêm bằng System.Data.Entity; sang global.asax. Cảm ơn bạn.
Tiến sĩ MAF

Nó hoạt động. Thêm mã đơn giản ở trên trên Global.asax, đó là tất cả, Không cần nhập bằng System.Data.Entity;
Hemant Ramphul

52

Đưa ra câu trả lời đúng là một cách để thực hiện, tuy nhiên đó là quá mức cần thiết khi bạn có thể sửa nó bằng một cài đặt cấu hình.

Tốt hơn nên sử dụng nó trong hàm tạo dbcontext

public DbContext() // dbcontext constructor
            : base("name=ConnectionStringNameFromWebConfig")
{
     this.Configuration.LazyLoadingEnabled = false;
     this.Configuration.ProxyCreationEnabled = false;
}

Lỗi API Web Asp.Net: Loại 'ObjectContent`1' không thể tuần tự hóa phần thân phản hồi cho loại nội dung 'application / xml; charset = utf-8 '


mã của bạn sẽ bị xóa nếu chúng tôi cập nhật mô hình từ cơ sở dữ liệu.
Bimal Das

1
Bạn có thể tách nó ra dễ dàng bằng cách xóa các tệp .tt và có ngữ cảnh riêng biệt. Mỗi khi bạn tạo mô hình, chỉ cần thêm lớp mới vào vị trí. @Brimal: Bạn có thể theo dõi youtube.com/watch?v=yex0Z6qwe7A
Md. Alim Ul Karim

1
Để tránh bị ghi đè, bạn có thể tắt tính năng tải chậm từ các thuộc tính edmx. Nó đã làm việc cho tôi.
Francisco G

@FranciscoG nó hoạt động nhưng nó sẽ bị mất nếu chúng ta xóa edmx và tạo lại.
Md. Alim Ul Karim,

1
@BimalDas hãy thử youtube.com/… này . Nó sẽ không loại bỏ
Md Alim Ul Karim.

37

Thêm mã này vào global.asaxbên dưới trênApplication_Start :

Cập nhật từ .Ignoređến .Serialize. Nó phải hoạt động.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
            GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

1
Cảm ơn bạn rất nhiều !, Bạn có thể giải thích rõ hơn tại sao chúng tôi phải xóa Xml Formatter để làm cho nó hoạt động không?
Jav_1

Không cần thêm bộ tuần tự json (ít nhất là trong trường hợp của tôi) nhưng cần loại bỏ định dạng Xml. Tôi đoán rằng trình tuần tự xml không thể tuần tự hóa các loại ẩn danh và bằng cách xóa nó, kết quả là tuần tự hóa thành json. Nếu suy đoán của tôi là chính xác, người ta có thể lấy dữ liệu ra khỏi bộ điều khiển bằng cách yêu cầu loại MIME "application / json".
LosManos

11
public class UserController : ApiController
{

   Database db = new Database();

   // construction
   public UserController()
   {
      // Add the following code
      // problem will be solved
      db.Configuration.ProxyCreationEnabled = false;
   }

   public IEnumerable<User> GetAll()
    {
            return db.Users.ToList();
    }
}

Wow, nó hiệu quả với tôi. Nhưng tại sao? Thuộc tính ProxyCreationEnabled làm gì?
jacktric

đã làm việc với tôi nhưng mã này là gì? tôi cũng lưu ý rằng tất cả các lớp con được truy xuất bằng null !!
Waleed A. Elgalil

10

Tôi không thích mã này:

foreach(var user in db.Users)

Để thay thế, người ta có thể làm điều gì đó như thế này, điều này đã hiệu quả với tôi:

var listOfUsers = db.Users.Select(r => new UserModel
                         {
                             userModel.FirstName = r.FirstName;
                             userModel.LastName = r.LastName;

                         });

return listOfUsers.ToList();

Tuy nhiên, cuối cùng tôi đã sử dụng giải pháp của Lucas Roselli.

Cập nhật: Đơn giản hóa bằng cách trả về một đối tượng ẩn danh:

var listOfUsers = db.Users.Select(r => new 
                         {
                             FirstName = r.FirstName;
                             LastName = r.LastName;
                         });

return listOfUsers.ToList();

10

Tôi đã giải quyết nó bằng cách sử dụng mã này cho tệp WebApiConfig.cs

var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects; 
config.Formatters.Remove(config.Formatters.XmlFormatter);

cảm ơn rất nhiều. không biết điều này làm gì đối với bảo mật.
Arun Prasad ES

6

Cũng có trường hợp này tạo ra lỗi tương tự:

Trong trường hợp trở lại là một List<dynamic> phương thức api web

Thí dụ:

public HttpResponseMessage Get()
{
    var item = new List<dynamic> { new TestClass { Name = "Ale", Age = 30 } };

    return Request.CreateResponse(HttpStatusCode.OK, item);
}

public class TestClass
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Vì vậy, đối với trường hợp này, hãy sử dụng [knownTypeAttribute] trong lớp trả về (tất cả chúng) như sau:

[KnownTypeAttribute(typeof(TestClass))]
public class TestClass
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Điều này làm việc cho tôi!


Điều gì sẽ xảy ra nếu bạn đang sử dụng linq pivot với các cột động?
codegrid

[KnownTypeAttribute (typeof (TestClass))] đã giải quyết được vấn đề của tôi
Guillaume Raymond

6

Thêm điều này vào Application_Start()phương thức Global.asaxtệp của bạn sẽ giải quyết được vấn đề

protected void Application_Start()
{
    GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
        .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    GlobalConfiguration.Configuration.Formatters
        .Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter); 
// ...
}

PHƯƠNG PHÁP 2: [Không được khuyến nghị]
Nếu bạn đang làm việc với EntityFramework, bạn có thể tắt proxy trong phương thức khởi tạo lớp DbContext của mình. LƯU Ý: mã này sẽ bị xóa nếu bạn cập nhật mô hình

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.ProxyCreationEnabled = false;
  }
}

1
Cảm ơn Suman. Tôi có những vấn đề giống nhau. Tôi đang kiểm tra API web của mình và gặp sự cố này. giải pháp của bạn giải quyết vấn đề. cảm ơn rất nhiều.
TarakPrajapati

4

Yêu thích cá nhân của tôi: Chỉ cần thêm mã dưới đây vào App_Start/WebApiConfig.cs. Điều này sẽ trả về json thay vì XML theo mặc định và cũng ngăn lỗi bạn mắc phải. Không cần chỉnh sửa Global.asaxđể loại bỏ, XmlFormatterv.v.

Loại 'ObjectContent`1' không thể tuần tự hóa phần thân phản hồi cho loại nội dung 'application / xml; charset = utf-8

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));

2

Sử dụng AutoMapper ...

public IEnumerable<User> GetAll()
    {
        using (Database db = new Database())
        {
            var users = AutoMapper.Mapper.DynamicMap<List<User>>(db.Users);
            return users;
        }
    }

2

Sử dụng không gian tên sau:

using System.Web.OData;

Thay vì :

using System.Web.Http.OData;

Nó đã làm việc cho tôi


2

Thêm dòng dưới đây

this.Configuration.ProxyCreationEnabled = false;

Hai cách để sử dụng ProxyCreationEnabled như false.

  1. Thêm nó vào bên trong DBContextConstructor

    public ProductEntities() : base("name=ProductEntities")
    {
        this.Configuration.ProxyCreationEnabled = false;
    }

HOẶC LÀ

  1. Thêm dòng bên trong Getphương thức

    public IEnumerable<Brand_Details> Get()
    {
        using (ProductEntities obj = new ProductEntities())
        {
            this.Configuration.ProxyCreationEnabled = false;
            return obj.Brand_Details.ToList();
        }
    }

2

Giải pháp phù hợp với tôi:

  1. Sử dụng [DataContract]cho lớp và [DataMember]các thuộc tính cho mỗi thuộc tính để tuần tự hóa. Điều này là đủ để nhận được kết quả Json (ví dụ: từ fiddler).

  2. Để có được tuần tự hóa xml, hãy viết Global.asaxmã này:

    var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter; xml.UseXmlSerializer = true;

  3. Đọc bài viết này, nó đã giúp tôi hiểu về tuần tự hóa: https://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization

1

Để thêm vào câu trả lời của jensendp:

Tôi sẽ chuyển thực thể cho một mô hình do người dùng tạo và sử dụng các giá trị từ thực thể đó để đặt các giá trị trong mô hình mới tạo của bạn. Ví dụ:

public class UserInformation {
   public string Name { get; set; }
   public int Age { get; set; }

   public UserInformation(UserEntity user) {
      this.Name = user.name;
      this.Age = user.age;
   }
}

Sau đó, thay đổi loại trả lại của bạn thành: IEnumerable<UserInformation>


1
có những cách tốt hơn để xử lý bản dịch cho bạn để bạn không phải duy trì mọi tài sản .. AutoMapper và ValueInjecter là 2 cách đáng chú ý
Sonic Soul

1

Đây là lỗi của tôi

Về cơ bản tôi thêm một dòng mà chúng là

  • các thực thể.Configuration.ProxyCreationEnabled = false;

tới UsersController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using UserDataAccess;

namespace SBPMS.Controllers
{
    public class UsersController : ApiController
    {


        public IEnumerable<User> Get() {
            using (SBPMSystemEntities entities = new SBPMSystemEntities()) {
                entities.Configuration.ProxyCreationEnabled = false;
                return entities.Users.ToList();
            }
        }
        public User Get(int id) {
            using (SBPMSystemEntities entities = new SBPMSystemEntities()) {
                entities.Configuration.ProxyCreationEnabled = false;
                return entities.Users.FirstOrDefault(e => e.user_ID == id);
            }
        }
    }
}

Đây là đầu ra của tôi:


1

Sử dụng [Serializable] cho lớp:

Thí dụ:

[Serializable]
public class UserModel {
    public string Name {get;set;}
    public string Age {get;set;}
}

Nó đã làm việc cho tôi!


1
Được diễn đạt như thế này, người ta gần như có thể nghĩ rằng tất cả những người trả lời bài đăng này trước đây đều là người câm;)… Chà, tôi thực sự nghi ngờ họ, vì vậy có lẽ giải pháp này đơn giản là không thể áp dụng khi câu hỏi được đặt ra, hồi năm 2015… Bản thân tôi không biết nhiều về cú pháp đó, nhưng tôi có cảm giác rằng nó tương đối mới hoặc có thể có một số nhược điểm khiến nó không sử dụng được trong một số trường hợp sử dụng nhất định. Tôi có cảm giác rằng giải pháp của bạn có thể hữu ích cho những độc giả trong tương lai tiếp cận câu hỏi này, nhưng nó chắc chắn sẽ hữu ích nếu bạn làm rõ những hạn chế của nó.
jwatkins

1

Bạn sẽ phải xác định Serializer Formatter trong WebApiConfig.cs có sẵn trong App_Start Folder như

Thêm config.Formatters.Remove (config.Formatters.XmlFormatter); // sẽ cung cấp cho bạn dữ liệu ở Định dạng JSON

Thêm config.Formatters.Remove (config.Formatters.JsonFormatter); // sẽ cung cấp cho bạn dữ liệu ở Định dạng XML


Bạn xứng đáng được nhận Huy chương.
TheKrogrammer

1

Chỉ cần đặt các dòng sau vào global.asax:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;  
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

Nhập khẩu

using System.Data.Entity;

0

Một trường hợp khác mà tôi nhận được lỗi này là khi truy vấn cơ sở dữ liệu của tôi trả về giá trị null nhưng kiểu mô hình người dùng / chế độ xem của tôi được đặt là không thể null. Ví dụ, thay đổi lĩnh vực UserModel tôi từ intđể int?giải quyết.


0

Điều này cũng xảy ra khi Loại phản hồi không được công khai! Tôi đã trả lại một lớp nội bộ khi tôi sử dụng Visual Studio để tạo cho tôi loại.

internal class --> public class

0

Mặc dù tất cả các câu trả lời ở trên đều đúng, người ta có thể muốn kiểm tra InnerException> ExceptionMessage .

Nếu nó nói như thế này " Cá thể ObjectContext đã bị xử lý và không còn có thể được sử dụng cho các hoạt động yêu cầu kết nối. ". Đây có thể là một vấn đề do hoạt động mặc định của EF.

Bằng cách gán LazyLoadingEnabled = false trong hàm khởi tạo DbContext của bạn sẽ thực hiện thủ thuật.

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.LazyLoadingEnabled = false;
  }
}

Để đọc chi tiết hơn về hành vi EagerLoading và LazyLoading của EF, hãy tham khảo Bài viết MSDN này .


0

Trong trường hợp của tôi, tôi đã gặp thông báo lỗi tương tự:

Loại 'ObjectContent`1' không thể tuần tự hóa phần thân phản hồi cho loại nội dung 'application / xml; charset = utf-8 '.

Nhưng khi tôi tìm hiểu sâu hơn, vấn đề là:

Nhập 'name.SomeSubRootType' với tên hợp đồng dữ liệu 'SomeSubRootType: //schemas.datacontract.org/2004/07/WhatEverService' không được mong đợi. Cân nhắc sử dụng DataContractResolver nếu bạn đang sử dụng DataContractSerializer hoặc thêm bất kỳ loại nào không được biết đến một cách tĩnh vào danh sách các loại đã biết - ví dụ: bằng cách sử dụng thuộc tính knownTypeAttribute hoặc bằng cách thêm chúng vào danh sách các loại đã biết được chuyển đến bộ tuần tự.

Cách tôi giải quyết bằng cách thêm vào KnownType.

[KnownType(typeof(SomeSubRootType))]
public partial class SomeRootStructureType

Điều này đã được giải quyết lấy cảm hứng từ câu trả lời này .

Tham khảo: https://msdn.microsoft.com/en-us/library/ms730167(v=vs.100).aspx


0

Visual Studio 2017 hoặc 2019 là hoàn toàn thiếu suy nghĩ về điều này, bởi vì Visual Studio chính nó yêu cầu đầu ra ở định dạng json , trong khi định dạng mặc định của Visual Studio là " XmlFormat" (config.Formatters.XmlFormatter) .

Visual Studio nên thực hiện việc này một cách tự động thay vì mang lại cho các nhà phát triển quá nhiều rắc rối.

Để khắc phục sự cố này, hãy truy cập WebApiConfig.cs tệp và thêm

var json = config.Formatters.JsonFormatter; json.SerializerSettings.PreserveRefutionsHandling = Newtonsoft.Json.PreserveRefutionsHandling.Objects; config.Formatters.Remove (config.Formatters.XmlFormatter);

sau " config.MapHttpAttributeRoutes (); " trong phương thức Đăng ký (cấu hình HttpConfiguration) . Điều này sẽ cho phép dự án của bạn tạo ra đầu ra json.


0

Trong trường hợp của tôi, tôi đã giải quyết việc tạo lại cơ sở dữ liệu. Tôi đã thực hiện một số thay đổi trong một mô hình và khởi chạy Cập nhật-Cơ sở dữ liệu trong Bảng điều khiển Trình quản lý Gói, Tôi gặp lỗi sau:

"Câu lệnh ALTER TABLE xung đột với ràng buộc FOREIGN KEY" FK_dbo.Actiilities_dbo.Projects_ProjectId ". Xung đột xảy ra trong cơ sở dữ liệu" TrackEmAllContext-20190530144302 ", bảng" dbo.Projects ", cột 'Id'."


0

Trong trường hợp: Nếu thêm mã vào WebApiConfig.cs hoặc Global.asax.cs không hiệu quả với bạn:

.ToList();

Thêm hàm .ToList ().

Tôi đã thử mọi giải pháp nhưng sau đây hiệu quả với tôi:

var allShops = context.shops.Where(s => s.city_id == id)**.ToList()**;
return allShops;

Tôi hy vọng, nó sẽ giúp.


0

trong trường hợp của tôi, nó đã được sửa khi tôi xóa từ khóa ảo trước các thuộc tính điều hướng của mình, ý tôi là các bảng tham chiếu. vì vậy tôi đã thay đổi

public virtual MembershipType MembershipType { get; set; }

đến:

public MembershipType MembershipType { get; set; }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.