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();
}
}
I'd suggest striving for more separation of concerns.
You have two concerns here:
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.
Consider using multiple projects to separate concerns more clearly.
MyNamespace.MyProject
- Class Library project that will hold your domain logic.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.
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:
MyNamespace.MyProject.Person
- Your domain layer representation of a Person.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.
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.
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.
Neither of these libraries is required to follow the ideas of this answer, but can make life much easier.
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);
}
}
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With