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!

5 Answers 5

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)));
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

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.
@IanMercer It will not if ObjectId is nullable
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()

2 Comments

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).
Original question was in the context ASP.NET MVC and standard code that would allow controller to parse ObjectId from url.
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.

Comments

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.

1 Comment

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...
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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.