Matthias Matthias - 22 days ago 10
ASP.NET (C#) Question

Custom Data Type for "HttpGet" Route in Asp.Net Web Api Project

I try to add a variable of "custom data type" in my HttpGet Route.

I have this code:

[HttpGet("{idObject}")]
public ResponseSchema Get(ObjectId idObject)
{
if (idObject == null) {
throw new BodyParseException();
}

var user = _usersLogic.GetById(idObject);

if (user == null) {
_response.Success = false;
_response.ErrorCode = "UserDoesNotExist";
}
else {
_response.Objects.Add(user);
}

return _response;
}


ObjectId is a Datatype defined in using MongoDB.Bson.

For the Json Serialization and Deserialization we already have the code to automatically convert on both sides. But can this be similarly done in the Url itself.

We are right now using this Mvc version:

"Microsoft.AspNet.Mvc": "6.0.0-beta8"


So the URL looks like this:

GET Users/55b795827572761a08d735ac


The code to parse it from "string" to "ObjectId" is:

ObjectId.TryParse(idString, out idObject);


The question is where to put that TryParse code. Because I need to tell ASP.NET how it should parse the idObject from String to ObjectId. Since the URL basically is a string.

For Post or Put JSON Payload I already found a solution. I know that this is something different. But Probably it is helpful to understand the scenario, or find a solution to this scenario:

public class EntityBaseDocument
{
[JsonConverter(typeof(ObjectIdConverter))]
public ObjectId Id { get; set; }
}

// Since we have this value converter. We can use ObjectId everywhere
public class ObjectIdConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString());
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
return new ObjectId(token.ToObject<string>());
}

public override bool CanConvert(Type objectType)
{
return typeof(ObjectId).IsAssignableFrom(objectType);
}
}

Answer

This will bind it from Uri objects:

public ResponseSchema Get([FromUri]ObjectId idObject)

So: ?param1=something&param2=sometingelse

This will bind it from the body (e.g. a JSon object)

public ResponseSchema Get([FromBody]ObjectId idObject)

Or you can roll your own:

public ResponseSchema Get([ModelBinder(typeof(MyObjectBinder))]ObjectId idObject)

The example on asp.net of a model binder is:

public class GeoPointModelBinder : IModelBinder
{
    // List of known locations.
    private static ConcurrentDictionary<string, GeoPoint> _locations
        = new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);

    static GeoPointModelBinder()
    {
        _locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
        _locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
        _locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
    }

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof(GeoPoint))
        {
            return false;
        }

        ValueProviderResult val = bindingContext.ValueProvider.GetValue(
            bindingContext.ModelName);
        if (val == null)
        {
            return false;
        }

        string key = val.RawValue as string;
        if (key == null)
        {
            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName, "Wrong value type");
            return false;
        }

        GeoPoint result;
        if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
        {
            bindingContext.Model = result;
            return true;
        }

        bindingContext.ModelState.AddModelError(
            bindingContext.ModelName, "Cannot convert value to Location");
        return false;
    }
}