简体   繁体   中英

JSON API action parameter for unknown object

I am creating a generic JSON API to handle any object with as little additional coding per object required. Here is the BaseController from which all other controllers (EmployeeController, ProductController, etc.) inherit:

public partial class BaseController<T> : Controller where T : ModelBase<T>, new()
{
    [RestHttpVerbFilter]
    public ActionResult Index (long? Id, string Property, long? PropertyId, T Model, string Format, string HttpVerb)
    {            
        switch (HttpVerb)
        {
            case "GET":
                return Json(Get(Id.Value, Property, PropertyId), JsonRequestBehavior.AllowGet);
            case "POST":
                return Json(Post(Id, Property, PropertyId, Model));
            case "PUT":
                return Json(Put(Id.Value, Property, PropertyId, Model));
            case "DELETE":
                return Json(Delete(Id, Property, PropertyId));
        }
        return Json(new { error = "Unknown HTTP verb" });
    }        

    internal object Post (long? Id, string Property, long? PropertyId, T Model)
    {
        if (!Id.HasValue && string.IsNullOrEmpty(Property) && !PropertyId.HasValue && Repository.Add(Model))
        {
            return Get(Model.ID);
        }            
        return new { error = "Unable to save new " + typeof(T).Name };
    }

    internal object Put (long? Id, string Property, long? PropertyId, T Model)
    {
        if (Id.HasValue)
        {
            Model.ID = Id.Value;
            if (string.IsNullOrEmpty(Property) && !PropertyId.HasValue && Repository.Update(Model))
            {
                return Get(Id.Value);
            }
        }
        return new { error = "Unable to update " + typeof(T).Name };
    }

I've removed some irrelevant code (Get, Delete). Right now, I have it set up so you can make GET requests to something like http://example.com/API/Venue/43/Events , which will return all the events at the Venue specified by the Id of 43. You are also able to POST to http://example.com/API/Venue to create a new Venue. I would like to be able to POST to http://example.com/API/Venue/43/Events to create a new Event for that Venue.

In the parameters for the Index action, right now the Model object picks up the Venue when it is posted. When I POST an Event object to the action, the Model parameter still picks it up. I have tried replacing T Model with the following, resulting in the following issues:

  • object Model , then casting it based on what URL it was POST ed to. This gives a System.InvalidCastException
  • dynamic Model , then casting it with (T)Model . This also gives a System.InvalidCastException
  • dynamic Model , then casting it with Model as T . Model is then null , meaning the cast failed
  • IModelBase Model , an interface, which doesn't work because the modelbinder tries to instantiate it

I would have thought that a dynamic would have worked, but I can't seem to avoid the InvalidCastException . The above code works, BTW, for the controller's object ( POST ing a Venue to http://example.com/API/Venue ).

This might have been a little vague, but hopefully my solution will help anybody who happens to unfortunately end up here. Firstly, I removed the Model parameter from the Index action. I then created the following helper class:

internal static class Deserializer
{
    internal static Dictionary<string, string> Deserialize (System.Web.HttpRequestBase Request)
    {
        Request.InputStream.Position = 0;
        string Json = new StreamReader(Request.InputStream).ReadToEnd();
        return new JavaScriptSerializer().Deserialize<Dictionary<string, string>>(Json);
    }
}

I then discovered, in the internal Post method, what URL was being POST ed to, then used the following helper class to construct the proper object:

internal static class ModelBuilder
{
    internal static object Build (Dictionary<string, string> Model, Type ModelType)
    {
        var Instance = Activator.CreateInstance(ModelType);
        foreach (var Property in ModelType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
        {
            var PropertyType = Property.PropertyType;
            if (Model.ContainsKey(Property.Name))
            {
                Property.SetValue(Instance, Convert.ChangeType(Model[Property.Name], PropertyType), null);
            }
        }
        return Instance;
    }
}

This needs to be refactored to deal with weird cases, but I have not run into any problems so far.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM