Late to the party, but since there isn't much about using HashIds within ASP.NET MVC I'll share my solution using a custom ModelBinder
and a BaseModel
class. The end route looks something like /example/voQ/details
.
First you need a model, that your existing models can extend from and generate a HashId;
public class abstract BaseModel
{
private static readonly Hashids __hashId = new Hashids("seed", 2);
public Id { get; set; }
[NotMapped]
public HashId
{
get { return BaseModel.__hashId.Encode(this.Id); }
}
}
The binder needs registering in Global.asax
for each model:
ModelBinders.Binders.Add(typeof(ExampleModel), new ControllerModelBinder<ExampleModel>());
Then the action can use the model directly without worrying about the hash id:
public ActionResult Details(ExampleModel model)
{
return View(model);
}
Setting up a link is the same, but rather than passing the Id
, you need to use the HashId
property from the BaseModel
.
@Url.Action("Details", new { id = item.HashId })
Finally the the model binder:
public class ControllerModelBinder<T> : DefaultModelBinder
where T : BaseModel
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(T))
{
string hash = bindingContext.ValueProvider.GetValue("id").RawValue.ToString();
if (!string.IsNullOrWhiteSpace(hash))
{
int id = HashIdHelper.ToInt(hash);
if (id > 0)
{
using (ApplicationContext context = new ApplicationContext())
{
DbRawSqlQuery<T> query = context.Database.SqlQuery<T>(string.Format("SELECT * FROM {0} WHERE id = @Id LIMIT 1", EntityHelper.GetTableName<T>(context)), new MySqlParameter("@Id", id));
try
{
T model = query.Cast<T>().FirstOrDefault();
if (model != null)
{
return model;
}
}
catch (Exception ex)
{
if (ex is ArgumentNullException || ex is InvalidCastException)
{
return base.BindModel(controllerContext, bindingContext);
}
throw;
}
}
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}