在Web.API控制器中自动反序列化为类似字符串的类

我有一个Web.API端点,它将这样的对象作为参数:

public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public UserName UserName { get; set; } } 

例如:

 [Route("api/person")] [AcceptVerbs("POST")] public void UpdatePerson(Person person) { // etc. } 

(这只是一个例子 – 我们实际上并没有通过我们的Web.API端点接受用户名)

我们的UserName类是一个将string隐式运算符定义的对象,因此我们将它视为整个应用程序中string的处理方式。

遗憾的是,Web.API不会自动知道如何将相应的JavaScript Person对象反序列化为C# Person对象 – 反序列化的C# Person对象始终为null。 例如,以下是我如何使用jQuery从我的JavaScript前端调用此端点:

 $.ajax({ type: 'POST', url: 'api/test', data: { FirstName: 'First', LastName: 'Last', Age: 110, UserName: 'UserName' } }); 

如果我不使用UserName属性,则将data参数正确反序列化为C# Person对象( UserName属性设置为null )。

如何使Web.API正确地将JavaScript对象上的UserName属性反序列化为我们的自定义UserName类?


这是我的UserName类的样子:

 public class UserName { private readonly string value; public UserName(string value) { this.value = value; } public static implicit operator string (UserName d) { return d != null ? d.ToString() : null; } public static implicit operator UserName(string d) { return new UserName(d); } public override string ToString() { return value != null ? value.ToUpper().ToString() : null; } public static bool operator ==(UserName a, UserName b) { // If both are null, or both are same instance, return true. if (System.Object.ReferenceEquals(a, b)) return true; // If one is null, but not both, return false. if (((object)a == null) || ((object)b == null)) return false; return a.Equals(b); } public static bool operator !=(UserName a, UserName b) { return !(a == b); } public override bool Equals(object obj) { if ((obj as UserName) == null) return false; return string.Equals(this, (UserName)obj); } public override int GetHashCode() { string stringValue = this.ToString(); return stringValue != null ? stringValue.GetHashCode() : base.GetHashCode(); } } 

您需要为UserName类编写自定义Json.NET转换器 。 在创建自定义转换器之后,您需要告诉Json.NET。 在我的一个项目中,我们在Global.asax.cs文件中的Application_Start方法中添加了以下代码行,让Json.NET了解转换器:

 // Global Json.Net config settings. JsonConvert.DefaultSettings = () => { var settings = new JsonSerializerSettings(); // replace UserNameConverter with whatever the name is for your converter below settings.Converters.Add(new UserNameConverter()); return settings; }; 

这是一个应该工作(未经测试)的快速和基本的实现。 几乎可以肯定的是:

 public class UserNameConverter : JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var username = (UserName)value; writer.WriteStartObject(); writer.WritePropertyName("UserName"); serializer.Serialize(writer, username.ToString()); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Variables to be set along with sensing variables string username = null; var gotName = false; // Read the properties while (reader.Read()) { if (reader.TokenType != JsonToken.PropertyName) { break; } var propertyName = (string)reader.Value; if (!reader.Read()) { continue; } // Set the group if (propertyName.Equals("UserName", StringComparison.OrdinalIgnoreCase)) { username = serializer.Deserialize(reader); gotName = true; } } if (!gotName) { throw new InvalidDataException("A username must be present."); } return new UserName(username); } public override bool CanConvert(Type objectType) { return objectType == typeof(UserName); } } 

WebAPI可以序列化和序列化类型化结构。 你需要做的是遵循打字模式。 例如在Javacsript中,我可以创建像Person这样的对象

 var person = { userName: 'bob123', firstName: 'Bobby', lastName: 'Doe' } 

然后将其作为我的请求的一部分传递给webAPI

在webAPI中,类型定义为:

 [Route("api/membershipinfo/getuserdata")] [HttpPost] public IHttpActionResult DoSomething([FromBody]Person p) { try { ...rest of your code here 

如果你有.net Type Person并且它与你在javascript请求名称/属性中创建的匹配,它将可用于映射。

关于shell的注意事项 我遵循camelCasing模式,因此第一个字符始终是小写的。 在您的点网类型中,您不需要执行此操作WebAPI将允许您通过配置来解释此问题。

我如何实现它是在我的webapi.config中使用自定义配置格式化程序,它有助于在序列化过程中转换类型

  //source: http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization // Replace the default JsonFormatter with our custom one ConfigJsonFormatter(config.Formatters); } private static void ConfigJsonFormatter(MediaTypeFormatterCollection formatters) { var jsonFormatter = formatters.JsonFormatter; var settings = jsonFormatter.SerializerSettings; settings.Formatting = Formatting.Indented; settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); settings.TypeNameHandling = TypeNameHandling.Auto; } 

我建议争取更多的关注点分离。

你有两个问题:

  1. 处理HTTP请求和响应。
  2. 执行域逻辑。

WebAPI关注处理HTTP请求和响应。 它向消费者提供了一份合同,规定了他们如何使用其端点和操作。 它不应该关心做任何其他事情。

项目管理

考虑使用多个项目更清楚地分离问题。

  1. MyNamespace.MyProject – 将保存域逻辑的类库项目。
  2. MyNamespace.MyProject.Service – 仅包含Web服务的WebAPI项目。

MyNamespace.MyProject上添加对MyNamespace.MyProject.Service的引用。 这将有助于您保持清晰的关注点分离。

不同的类

现在,重要的是要了解您将拥有两个具有相同名称的类,但它们是不同的。 完全合格,他们的区别变得清晰:

  1. MyNamespace.MyProject.Person – 您的Person图层表示。
  2. MyNamespace.MyProject.Service.Models.Person – 您的WebAPI合同人员代表。

您的域图层对象:

 namespace MyNamespace.MyProject { public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public UserName UserName { get; set; } } } 

您的服务层对象:

 namespace MyNamespace.MyProject.Service.Models { public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } //The service contract expects username to be a string. public string UserName { get; set; } } } 

这里的好处是域层表示可以独立于WebAPI合同进行更改。 因此,您的消费者合同不会改变。

从服务逻辑中隔离域逻辑

我还建议将作用于传入Person任何域逻辑移动到域逻辑类库。 这也允许此逻辑在可能超出WebAPI范围的其他应用程序和库中重用。 此外,为了继续将我们的域逻辑与服务逻辑分离,我将实现Repository模式,并创建MyNamespace.MyProject.PersonRepository定义如何处理域级别Person对象的存储库。

您的控制器现在可能看起来像这样:

 [Route("api/person")] [HttpPost] public void UpdatePerson(Models.Person person) { var mappedPerson = Mapper.Map(person); personRepository.Update(mappedPerson); //I'd suggest returning some type of IHttpActionResult here, even if it's just a status code. } 

Mapper.Map(person)来自AutoMapper 。 您首先应用程序启动时在某个配置类中设置映射。 这些映射将告诉AutoMapper如何将MyNamespace.MyProject.Service.Models.Person转换为MyNamespace.MyProject.Person

 //This gets called once somewhere when the application is starting. public static void Configure() { // Mapper.Create() //Additional mappings. .ForMember(dest => dest.Username, opt => opt.MapFrom(src => new UserName(src.UserName))) } 

此外,您可能需要使用Singleton,Service Locator或Inversion of Control(IoC)容器(如Ninject)来获取对personRepository的引用。 我强烈建议使用IoC。 Ninject有一个包可以接管为WebAPI创建控制器,注入您已配置的依赖项。

我们在这里完成的是我们已经将所有域逻辑移出MyNamespace.MyProject.ServiceMyNamespace.MyProject现在可以独立测试,甚至可以包含在其他项目中,而不会带来WebAPI依赖项。 我们已经明确区分了问题。


关于类命名的注意事项

相同的类名可能会让某些团队感到困惑。 您可以选择实现某种类型的命名约定以使名称更清晰,例如将DTOModel附加到服务层中的类。 我更喜欢将它们放在不同的命名空间中,并根据需要对它们进行限定。


第三方图书馆参考

  1. AutoMapper – 用于减少将服务对象映射到域对象的样板,反之亦然。
  2. Ninject – 用于将依赖项注入控制器(记得也可以获取WebAPI或OWIN包)。 可以使用任何IoC。 或者,也可以使用Singleton或Service Locator模式,但可能会使测试变得困难。

这些图书馆都不需要遵循这个答案的想法,但可以让生活更轻松。