How to pass ObjectId from MongoDB in MVC.net
Asked Answered
C

5

9

I'm starting a new project with Mongo, NoRM and MVC .Net.

Before I was using FluentNHibernate so my IDs were integer, now my IDs are ObjectId. So when I have an Edit link my URL looks like this :

WebSite/Admin/Edit/23,111,160,3,240,200,191,56,25,0,0,0

And it does not bind automaticly to my controller as an ObjectId

Do you have any suggestions/best practices to work with this? Do I need to encode/decode the ID everytime?

Thanks!

Cartilage answered 29/6, 2010 at 17:36 Comment(0)
C
14

I Use following

public class ObjectIdModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        string value = controllerContext.RouteData.Values[bindingContext.ModelName] as string;
        if (String.IsNullOrEmpty(value)) {
            return ObjectId.Empty;
        }
        return new ObjectId(value);
    }
}

and

protected void Application_Start()
    {
        ......

        ModelBinders.Binders.Add(typeof(ObjectId), new ObjectIdModelBinder()); 
    }

almost forgot, make URLs from ObjectId.ToString()

Cannoneer answered 15/7, 2010 at 5:48 Comment(2)
This solution does not work when the values are passed in a Form. See my answer for a solution that works no matter how the values are passed. (Might also be worth noting which C# MongoDB driver this was for as their syntax varies).Nibbs
Original question was in the context ASP.NET MVC and standard code that would allow controller to parse ObjectId from url.Cannoneer
N
16

Use a custom model binder like this ... (working against the offical C# MongoDB driver)

protected void Application_Start()
{
    ...
    ModelBinders.Binders.Add(typeof(ObjectId), new ObjectIdModelBinder()); 
}

public class ObjectIdModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (result == null)
        {
            return ObjectId.Empty;
        }
        return ObjectId.Parse((string)result.ConvertTo(typeof(string)));
    }
}
Nibbs answered 4/12, 2010 at 7:9 Comment(2)
This throws an exception when the parameter is fiddled with. I think using TryParse and returning ObjectId.Empty when it's invalid is a better way of handling it.Indult
@IanMercer It will not if ObjectId is nullableCancan
C
14

I Use following

public class ObjectIdModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        string value = controllerContext.RouteData.Values[bindingContext.ModelName] as string;
        if (String.IsNullOrEmpty(value)) {
            return ObjectId.Empty;
        }
        return new ObjectId(value);
    }
}

and

protected void Application_Start()
    {
        ......

        ModelBinders.Binders.Add(typeof(ObjectId), new ObjectIdModelBinder()); 
    }

almost forgot, make URLs from ObjectId.ToString()

Cannoneer answered 15/7, 2010 at 5:48 Comment(2)
This solution does not work when the values are passed in a Form. See my answer for a solution that works no matter how the values are passed. (Might also be worth noting which C# MongoDB driver this was for as their syntax varies).Nibbs
Original question was in the context ASP.NET MVC and standard code that would allow controller to parse ObjectId from url.Cannoneer
P
0

I am not familiar with the ObjectId type but you could write a custom model binder that will take care of converting the id route constraint to an instance of ObjectId.

Pikeperch answered 29/6, 2010 at 17:39 Comment(0)
E
0

Did you know you can use the [MongoIdentifier] attribute to make any property act as the unique key?

I've been solving this issue by borrowing a technique from WordPress by having every entity also be represented by a "url slug" property and decorating that property with [MongoIdentifier].

So if I had a person named Johnny Walker I'd create a slug of "johnny-walker". You just have to make sure these url slugs stay unique and you get to keep clean urls without ugly object ids.

Engel answered 29/6, 2010 at 18:34 Comment(1)
Yup I know that, since it's only for the administration I don't need to have clean URLs. For now, I added a IdValue property that render the ObjectId value as a string and when I need to fetch the data I just do .Single(new ObjectId(id)) not sure if it's the best way but it works fine...Cartilage
P
0

For Web API you can add Custom parameter binding ule in WebApiConfig:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //...
        config.ParameterBindingRules.Insert(0, GetCustomParameterBinding);
        //...
    }

    public static HttpParameterBinding GetCustomParameterBinding(HttpParameterDescriptor descriptor)
    {
        if (descriptor.ParameterType == typeof(ObjectId))
        {
            return new ObjectIdParameterBinding(descriptor);
        }
        // any other types, let the default parameter binding handle
        return null;
    }

    public class ObjectIdParameterBinding : HttpParameterBinding
    {
        public ObjectIdParameterBinding(HttpParameterDescriptor desc)
            : base(desc)
        {
        }

        public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            try
            {
                SetValue(actionContext, new ObjectId(actionContext.ControllerContext.RouteData.Values[Descriptor.ParameterName] as string));
                return Task.CompletedTask;
            }
            catch (FormatException)
            {
                throw new BadRequestException("Invalid ObjectId format");
            }
        }
    }
}

And use it Without any additional attributes in controller:

 [Route("{id}")]
 public IHttpActionResult Get(ObjectId id)
Paphos answered 4/11, 2017 at 4:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.