RavenDB Ids and ASP.NET MVC3 Routes
Asked Answered
A

3

15

Just building a quick, simple site with MVC 3 RC2 and RavenDB to test some things out.

I've been able to make a bunch of projects, but I'm curious as to how Html.ActionLink() handles a raven DB ID.

My example: I have a Document called "reasons" (a reason for something, just text mostly), which has reason text and a list of links. I can add, remove, and do everything else fine via my repository.

Below is the part of my razor view that lists each reason in a bulleted list, with an Edit link as the first text:

@foreach(var Reason in ViewBag.ReasonsList)
{
    <li>@Html.ActionLink("Edit", "Reasons", "Edit", new { id = Reason.Id }, null) @Reason.ReasonText</li>
    <ul>
    @foreach (var reasonlink in Reason.ReasonLinks)
    { 
        <li><a href="@reasonlink.URL">@reasonlink.URL</a></li>
    }
    </ul>
}

The Problem

This works fine, except for the edit link. While the values and code here appear to work directly (i.e the link is firing directly), RavenDB saves my document's ID as "reasons/1".

So, when the URL happens and it passes the ID, the resulting route is "http://localhost:4976/Reasons/Edit/reasons/2". So, the ID is appended correctly, but MVC is interpreting it as its own route.

Any suggestions on how I might be able to get around this? Do I need to create a special route to handle it or is there something else I can do?

Appreciation answered 24/12, 2010 at 18:49 Comment(3)
Try this: How to work RavenDB Id with ASP.NET MVC RoutesUngodly
Perfect! I followed the second solution (putting the asterisk in front of the Id in Routes), but this article was exactly what I was looking for.) I'm going to add a second answer just to explain what I did exactly, but this is definitely the right answer. Thanks!Appreciation
@goober _ I've had that link in my bookmarks for a long time. Glad its finally being put to good use.Ungodly
B
13

I just downloaded the latest version of RavenDB and tried this out.

public class Entity {
    public int Id { get;set; }
    public string Text { get;set; }
}

When I saved it to RavenDB, the id in Raven was "entities/1", but the mapping in RavenDB client was able to successfully interpret the Id from what was in the database to the integer that I wanted.

var entity = session.Load<Entity>(1);
Assert.IsTrue(entity.Id == 1);

You do not need a extension method, and this way you would not need to alter any routes as mentioned above, because you will be dealing with good ol' integers. The string Ids were almost a deal breaker, but amazingly this works so RavenDB is back in the hunt.

  • Note: I figured this out from watching a Rob Ashton talk and realizing that all his document classes had integers as Ids.
Buffer answered 7/2, 2012 at 14:47 Comment(3)
+1 @Khalid Exactly this. Looking around in Ayende's Racoon Blog github.com/ayende/RaccoonBlog, I noticed him converting quite often between the native string Ids and integer Ids. Mostly for placing in routes but good ole ints have other uses as well.Perm
It doesn't work with includes unless you can translate the integer into the RavenDb string equivalent.Buffer
WARN! This approach with integer id in entity classes has a lot of painful side effects when starting working with indexes and map reduce etc. Its a pitfall and I strongly suggest using string Id:s and the convert to integer when necessary for presentation (urls etc). This post is very true IMHO after some experience of wrenching in integer ids myself.Anlage
J
2

I was having a similar issue and came up with my own solution before I found the link to Shiju Varghese blog post referenced by @jfar.

It might not be as clean and simple as the solutions provided in the blog post, but I do believe it can compete as a solution none the less. So here goes:

In a standard model class, when using RavenDB, we normally have an id property like so:

public string Id { get; set; }

What I did, was to add another id property like so:

public int? IdInt
{
    get { return int.Parse(Id.Substring(Id.IndexOf("/") + 1)); }
}

This will give us the number part of the given RavenDB id. I then had a class that looked something like this:

[Bind(Exclude = "IdInt")]
public class Item
{
    public string Id { get; set; }
    public int? IdInt
    {
        get { return int.Parse(Id.Substring(Id.IndexOf("/") + 1)); }
    }

    ...
}

Note that I have excluded the IdInt property, as I don't want the model binder to handle it. Furthermore, note, that the IdInt property is nullable. This is to avoid problems with the model binder later on, when we are creating new items and the Id property is null.

Then in the routes I declared a rule similar to this:

routes.MapRoute(
    "WithParam", // Route name
    "{controller}/{action}/{id}" // URL with parameters
);

Later on using an ActionLink or something similar we can do the following:

@Html.ActionLink("Details", "Details", new { id = item.IdInt })

And lastly when we press the rendered link, we are sent to the appropriate action which could look something like this:

public ActionResult Details(int id)
{
    var item = _session.Load<Item>(id);            
    return View(item);
}

The reason why this will work is because of the Load method, which takes a ValueType as parameter and then automatically resolves the number to the correct RavenDB id.

What we have achieved with this approach is a url that looks something like this:

/items/details/1

As I said in the beginning, this solution is less elegant than the previous suggested solutions, however, this approach will give you a cleaner url as you can work with the id's as you normally would.

Janellajanelle answered 5/7, 2011 at 22:11 Comment(1)
Wouldn't an extension method be better? GetMvcSafeId()Kwiatkowski
A
0

FYI, the link posted by @jfar was exactly the article. The following is the solution I used from the article:

Solution 2 - Modify ASP.NET MVC Route

Modify the ASP.NET MVC routes in the Global.asax.cs file, as shown in the following code:

 routes.MapRoute(
     "WithParam",                                           // Route name
     "{controller}/{action}/{*id}"                         // URL with parameters
     );  

We just put "*" in front of the id variable that will be working with the default Id separator of RavenDB

Appreciation answered 24/12, 2010 at 21:16 Comment(1)
this limits the way you want to do routes. For example, RESTful urls in Rails follow the convention of "localhost/1/edit" or nested resources "localhost/post/1/comment/3". That would be difficult with the catch all.Buffer

© 2022 - 2024 — McMap. All rights reserved.