Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatically deserialize to string-like class in Web.API controller

I have a Web.API endpoint that takes an object like this as a parameter:

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

For example:

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

(This is just an example - we're not actually accepting usernames through our Web.API endpoint)

Our UserName class is an object that defines implicit operators to string, so we treat it exactly as we would a string throughout our application.

Unfortunately, Web.API doesn't automatically know how to deserialize a corresponding JavaScript Person object into a C# Person object - the deserialized C# Person object is always null. For example, here's how I might call this endpoint from my JavaScript frontend, using jQuery:

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

If I leave off the UserName property, the data parameter is correctly deserialized into a C# Person object (with the UserName property set to null).

How can I make Web.API properly deserialize the UserName property on the JavaScript object into our custom UserName class?


Here what my UserName class looks like:

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();
    }
}
like image 299
Nathan Friend Avatar asked Dec 21 '15 16:12

Nathan Friend


3 Answers

I'd suggest striving for more separation of concerns.

You have two concerns here:

  1. Handling HTTP requests and responses.
  2. Performing domain logic.

WebAPI is concerned with handling HTTP requests and responses. It provides a contract to consumers specifying how they can consume its endpoints and actions. It shouldn't be concerned with doing anything else.

Project Management

Consider using multiple projects to separate concerns more clearly.

  1. MyNamespace.MyProject - Class Library project that will hold your domain logic.
  2. MyNamespace.MyProject.Service - WebAPI project that only contains your web service.

Add a reference to MyNamespace.MyProject on MyNamespace.MyProject.Service. This will help you maintain a clean separation of concerns.

Different Classes

Now, it's important to understand that you will have two classes with the same name, but are different. Fully qualified, their distinction becomes clear:

  1. MyNamespace.MyProject.Person - Your domain layer representation of a Person.
  2. MyNamespace.MyProject.Service.Models.Person - Your WebAPI contractual representation of a Person.

Your domain layer object:

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; }
    }
}

Your service layer object:

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; }
    }
}

The benefit here is that the domain layer representation can change independently of the WebAPI contract. Thus, your consumers contract does not change.

Isolate Domain Logic From Service Logic

I'd also suggest moving any domain logic that acts upon the incoming Person to your domain logic Class Library. This also allows this logic to be reused in other applications and libraries that may be outside the scope of WebAPI. Additionally, to continue with separating our domain logic from our service logic, I'd implement the Repository pattern, and create MyNamespace.MyProject.PersonRepository defining how to deal with your repository of domain level Person objects.

Your controller may now simply look like this:

[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.
}

The magic with Mapper.Map<Person>(person) comes from AutoMapper. You'd first setup your mappings in a configuration class somewhere at application start. These mappings would tell AutoMapper how to convert MyNamespace.MyProject.Service.Models.Person into MyNamespace.MyProject.Person.

//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)))
}

Furthermore, you'd likely need to use either a Singleton, Service Locator, or Inversion of Control (IoC) container like Ninject to get a reference to your personRepository. I strongly suggest using IoC. Ninject has a package that can take over the creation of controllers for WebAPI, injecting your dependencies that you have configured.

What we have accomplished here is that we have moved all the domain logic out of MyNamespace.MyProject.Service. MyNamespace.MyProject may now be tested independently, or even included in other projects without bringing along the WebAPI dependencies. We have achieved a clear separation of concerns.


Note on Class Naming

The identical class names can be confusing to some teams. You may choose to implement some type of naming convention to make the names more clear, like appending DTO or Model to the classes in your service layer. I prefer just placing them in different namespaces and qualifying them as needed.


Third Party Libraries Referenced

  1. AutoMapper - For reducing boilerplate in mapping service objects to domain objects, and vice-versa.
  2. Ninject - For injecting dependencies into controllers (remember to get the WebAPI or OWIN packages too). Any IoC can be used. Alternatively, a Singleton or Service Locator pattern could also be used, but may make testing difficult.

Neither of these libraries is required to follow the ideas of this answer, but can make life much easier.

like image 173
crush Avatar answered Nov 03 '22 03:11

crush


You need to write a custom Json.NET Converter for your UserName class. After you create the custom converter then you need to tell Json.NET about it. In one of my projects we added the following lines of code to the Application_Start method in your Global.asax.cs file to let Json.NET know about the converter:

// 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;
};

Here is quick and basic implementation of one that should work (untested). It almost certainly could be improved upon:

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);
    }
}
like image 38
Erik Avatar answered Nov 03 '22 05:11

Erik


WebAPI can serialize and serialize typed structure. What you have to do though is follow the typed pattern. For example in Javacsript I can create an object like Person

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

Then pass that as an object as part of my request to webAPI

In webAPI have the type defined as:

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

If you have the .net Type Person and it matches what you created in your javascript request name/property wise it will be available for mapping.

Note on the casing. I follow the camelCasing pattern so the first character is always lowercase. In your dot net type you don't need to do this WebAPI will allow you to account for this via configuration.

How I accomplished it was with a custom configuration formatter in my webapi.config which helps convert the type during serialization

    //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;
}
like image 20
rlcrews Avatar answered Nov 03 '22 03:11

rlcrews