首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在Web.API控制器中自动反序列化为类字符串

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

Stack Overflow用户
提问于 2015-12-21 16:26:10
回答 3查看 4.3K关注 0票数 11

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

代码语言:javascript
运行
复制
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public UserName UserName { get; set; }
}

例如:

代码语言:javascript
运行
复制
[Route("api/person")]
[AcceptVerbs("POST")]
public void UpdatePerson(Person person)
{
    // etc.
}

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

我们的UserName类是一个对象,它将隐式运算符定义为string,因此我们将它与整个应用程序中的string完全相同。

不幸的是,Web.API不知道如何将相应的JavaScript Person对象反序列化为C# Person对象--反序列化的C# Person对象始终为空。例如,下面是如何从我的JavaScript前端调用这个端点,使用jQuery:

代码语言:javascript
运行
复制
$.ajax({
    type: 'POST',
    url: 'api/test',
    data: { FirstName: 'First', LastName: 'Last', Age: 110, UserName: 'UserName' }
});

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

如何将UserName对象上的 UserName 属性正确地反序列化为自定义的UserName类?

这里我的UserName类是什么样子的:

代码语言:javascript
运行
复制
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();
    }
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-12-21 16:55:16

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

代码语言:javascript
运行
复制
// 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;
};

这里是一个应该工作的快速和基本的实现(未经测试)。几乎可以肯定的是,可以在以下方面加以改进:

代码语言:javascript
运行
复制
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<string>(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);
    }
}
票数 4
EN

Stack Overflow用户

发布于 2015-12-21 17:44:30

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

你在这里有两个问题:

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

WebAPI与处理HTTP请求和响应有关。它向消费者提供一个契约,指定他们如何使用它的端点和操作。它不应该关心做任何其他的事情。

项目管理

考虑使用多个项目来更清楚地分离关注点。

  1. MyNamespace.MyProject -类库项目,将保存您的域逻辑。
  2. MyNamespace.MyProject.Service - WebAPI项目,它只包含您的web服务。

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

不同类别

现在,重要的是要理解,您将有两个名称相同的类,但它们是不同的。完全合格,它们之间的区别变得明确:

  1. MyNamespace.MyProject.Person -一个人的域层表示。
  2. MyNamespace.MyProject.Service.Models.Person -你的WebAPI合同代表一个人。

域层对象:

代码语言:javascript
运行
复制
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; }
    }
}

您的服务层对象:

代码语言:javascript
运行
复制
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对象的存储库。

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

代码语言:javascript
运行
复制
[Route("api/person")]
[HttpPost]
public void UpdatePerson(Models.Person person)
{
    var mappedPerson = Mapper.Map<Person>(person);
    personRepository.Update(mappedPerson);

    //I'd suggest returning some type of IHttpActionResult here, even if it's just a status code.
}

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

代码语言:javascript
运行
复制
//This gets called once somewhere when the application is starting.
public static void Configure()
{
    //<Source, Destination>
    Mapper.Create<Models.Person, Person>()
        //Additional mappings.
        .ForMember(dest => dest.Username, opt => opt.MapFrom(src => new UserName(src.UserName)))
}

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

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

关于类命名的注记

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

第三方图书馆参考

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

这两个库都不需要遵循这个答案的思想,但可以使生活变得容易得多。

票数 2
EN

Stack Overflow用户

发布于 2015-12-21 16:52:23

WebAPI可以序列化和序列化类型化结构。不过,您要做的是遵循类型化模式。例如,在Javacsript中,我可以创建一个类似Person的对象

代码语言:javascript
运行
复制
var person = {
userName: 'bob123',
firstName: 'Bobby',
lastName: 'Doe'
}

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

在webAPI中,类型定义为:

代码语言:javascript
运行
复制
[Route("api/membershipinfo/getuserdata")]
[HttpPost]
 public IHttpActionResult DoSomething([FromBody]Person p)
 {
   try
     {
       ...rest of your code here

如果您拥有.net类型的Person,并且它与您在javascript请求名称/属性中创建的内容相匹配,那么它将可用于映射。

请注意机壳上的情况。我遵循camelCasing模式,所以第一个字符总是小写。在您的点网类型中,您不需要这样做,WebAPI将允许您通过配置对此进行解释。

我是如何在我的webapi.config中使用一个自定义配置格式化程序来完成它的,它有助于在序列化期间转换类型。

代码语言:javascript
运行
复制
    //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
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34400191

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档