2

I'm developing a simple web app where I need to bind all types implementing and interface of a specific type. My interface has one single property like this

public interface IContent {
    string Id { get;set; }
}

a common class using this interface would look like this

public class Article : IContent {
    public string Id { get;set; }
    public string Heading { get;set; }
}

to be clean here the article class is just one of many different classes implementing IContent so therefor I need a generic way of storing and updating these types.

So in my controller I have the put method like this

public void Put(string id, [System.Web.Http.ModelBinding.ModelBinder(typeof(ContentModelBinder))] IContent value)
{
    // Store the updated object in ravendb
}

and the ContentBinder

public class ContentModelBinder : System.Web.Http.ModelBinding.IModelBinder {
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {

        actionContext.ControllerContext.Request.Content.ReadAsAsync<Article>().ContinueWith(task =>
        {
            Article model = task.Result;
            bindingContext.Model = model;
        });

        return true; 
    }

}

The code above does not work because it does not seem to get hold of the Heading property even though if I use the default model binder it binds the Heading correctly.

So, in the BindModel method I guess I need to load the correct object from ravendb based on the Id and then update the complex object using some kind of default model binder or so? This is where I need some help.

4
  • @matt-johnson Actually I have no clue how to update the model but I have updated my question with some code but that is just for trial and error. Commented Mar 16, 2013 at 16:17
  • @MattJohnson Maybe it's possible to use HttpContent.ReadAsAsync<T> or something to deserialize the json to a specific type? Commented Mar 16, 2013 at 16:58
  • @matt-johnson I have updated my code with ReadAsAsync but I can't figure out why that doesn't work? Commented Mar 16, 2013 at 19:06
  • 1
    Hi Marcus, if you are sending/receiving data in Json, have you considered using the TypeNameHandling on Json media type formatter's SerializerSettings. This setting causes the type information to be present on the body: Example: {"$type":"Service.Article, ModelBindingTrials","Id":"A1","Heading":"algorithms"}. Your parameter/return type can be an interface here. ofcourse, this solution requires putting in the type information on the payload, but just of checking if you have considered it. Commented Mar 16, 2013 at 19:28

2 Answers 2

2

Marcus, following is an example which would work fine for both Json and Xml formatter.

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;

namespace Service
{
    class Service
    {
        private static HttpSelfHostServer server = null;
        private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);

        static void Main(string[] args)
        {
            HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
            config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
            config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
            config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;

            try
            {
                server = new HttpSelfHostServer(config);
                server.OpenAsync().Wait();

                Console.WriteLine("Service listenting at: {0} ...", baseAddress);

                TestWithHttpClient("application/xml");

                TestWithHttpClient("application/json");

                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception Details:\n{0}", ex.ToString());
            }
            finally
            {
                if (server != null)
                {
                    server.CloseAsync().Wait();
                }
            }
        }

        private static void TestWithHttpClient(string mediaType)
        {
            HttpClient client = new HttpClient();

            MediaTypeFormatter formatter = null;

            // NOTE: following any settings on the following formatters should match
            // to the settings that the service's formatters have.
            if (mediaType == "application/xml")
            {
                formatter = new XmlMediaTypeFormatter();
            }
            else if (mediaType == "application/json")
            {
                JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
                jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;

                formatter = jsonFormatter;
            }

            HttpRequestMessage request = new HttpRequestMessage();
            request.RequestUri = new Uri(baseAddress + "api/students");
            request.Method = HttpMethod.Get;
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
            HttpResponseMessage response = client.SendAsync(request).Result;
            Student std = response.Content.ReadAsAsync<Student>().Result;

            Console.WriteLine("GET data in '{0}' format", mediaType);
            if (StudentsController.CONSTANT_STUDENT.Equals(std))
            {
                Console.WriteLine("both are equal");
            }

            client = new HttpClient();
            request = new HttpRequestMessage();
            request.RequestUri = new Uri(baseAddress + "api/students");
            request.Method = HttpMethod.Post;
            request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
            Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;

            Console.WriteLine("POST and receive data in '{0}' format", mediaType);
            if (StudentsController.CONSTANT_STUDENT.Equals(std1))
            {
                Console.WriteLine("both are equal");
            }
        }
    }

    public class StudentsController : ApiController
    {
        public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };

        public Person Get()
        {
            return CONSTANT_STUDENT;
        }

        // NOTE: specifying FromBody here is not required. By default complextypes are bound
        // by formatters which read the body
        public Person Post([FromBody] Person person)
        {
            if (!ModelState.IsValid)
            {
                throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
            }

            return person;
        }
    }

    [DataContract]
    [KnownType(typeof(Student))]
    public abstract class Person : IEquatable<Person>
    {
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public string Name { get; set; }

        // this is ignored
        public DateTime DateOfBirth { get; set; }

        public bool Equals(Person other)
        {
            if (other == null)
                return false;

            if (ReferenceEquals(this, other))
                return true;

            if (this.Id != other.Id)
                return false;

            if (this.Name != other.Name)
                return false;

            return true;
        }
    }

    [DataContract]
    public class Student : Person, IEquatable<Student>
    {
        [DataMember]
        public List<string> EnrolledCourses { get; set; }

        public bool Equals(Student other)
        {
            if (!base.Equals(other))
            {
                return false;
            }

            if (this.EnrolledCourses == null && other.EnrolledCourses == null)
            {
                return true;
            }

            if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
                (this.EnrolledCourses != null && other.EnrolledCourses == null))
                return false;

            if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
                return false;

            for (int i = 0; i < this.EnrolledCourses.Count; i++)
            {
                if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
                    return false;
            }

            return true;
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0

I used @kiran-challa solution and added TypeNameHandling on Json media type formatter's SerializerSettings.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.