在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; }
我建议争取更多的关注点分离。
你有两个问题:
- 处理HTTP请求和响应。
- 执行域逻辑。
WebAPI关注处理HTTP请求和响应。 它向消费者提供了一份合同,规定了他们如何使用其端点和操作。 它不应该关心做任何其他事情。
项目管理
考虑使用多个项目更清楚地分离问题。
-
MyNamespace.MyProject
– 将保存域逻辑的类库项目。 -
MyNamespace.MyProject.Service
– 仅包含Web服务的WebAPI项目。
在MyNamespace.MyProject
上添加对MyNamespace.MyProject.Service
的引用。 这将有助于您保持清晰的关注点分离。
不同的类
现在,重要的是要了解您将拥有两个具有相同名称的类,但它们是不同的。 完全合格,他们的区别变得清晰:
-
MyNamespace.MyProject.Person
– 您的Person图层表示。 -
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
来自AutoMapper 。 您首先应用程序启动时在某个配置类中设置映射。 这些映射将告诉AutoMapper如何将MyNamespace.MyProject.Service.Models.Person
转换为MyNamespace.MyProject.Person
。
//This gets called once somewhere when the application is starting. public static void Configure() { //
此外,您可能需要使用Singleton,Service Locator或Inversion of Control(IoC)容器(如Ninject)来获取对personRepository
的引用。 我强烈建议使用IoC。 Ninject有一个包可以接管为WebAPI创建控制器,注入您已配置的依赖项。
我们在这里完成的是我们已经将所有域逻辑移出MyNamespace.MyProject.Service
。 MyNamespace.MyProject
现在可以独立测试,甚至可以包含在其他项目中,而不会带来WebAPI依赖项。 我们已经明确区分了问题。
关于类命名的注意事项
相同的类名可能会让某些团队感到困惑。 您可以选择实现某种类型的命名约定以使名称更清晰,例如将DTO
或Model
附加到服务层中的类。 我更喜欢将它们放在不同的命名空间中,并根据需要对它们进行限定。
第三方图书馆参考
- AutoMapper – 用于减少将服务对象映射到域对象的样板,反之亦然。
- Ninject – 用于将依赖项注入控制器(记得也可以获取WebAPI或OWIN包)。 可以使用任何IoC。 或者,也可以使用Singleton或Service Locator模式,但可能会使测试变得困难。
这些图书馆都不需要遵循这个答案的想法,但可以让生活更轻松。