Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding parameter to complex type

I have a navigation bar, with several links, like this:

<a href="MyController/Browse/2">MenuItem1</a>

This request would hit my action method:

public ActionResult Browse(int departmentId)
{
    var complexVM = MyCache.GetComplexVM(departmentId);
    return View(complexVM);
}

This is my ComplexVM:

public class ComplexVM 
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; }
}

MyCache, is a static list of departments, which I am keeping in memory, so when user passes in DepartmentId, I wouldn't need to get the corresponding DepartmentName from DB.

This is working fine... but it would be nice if I could somehow initialize ComplexVM in custom model binder, instead of initializing it in the Controller... so I still want to use a link (menu item), but this time, a CustomModelBinder binds my parameter, 2, to ComplexVM: it needs to look up the name of department with id = 2 from MyCache and initialize ComplexVM, then ComplexVM would be passed to this action method:

public ActionResult Browse(ComplexVM complexVM)
{
    return View(complexVM);
}

I want to hit the above controller without doing a post-back, as I have a lot of menu item links in my navigation bar... not sure if this is possible? Or if this is even a good idea?

I have seen this link, which sort of describes what I want... but I am not sure how the routing would work... i.e. routing id:2 => ComplexVM


Alternatively would it be possible to do this in RouteConfig, something like this:

routes.MapRoute(
    name: "Browse",
    url: "{controller}/Browse/{departmentId}",
    // this does not compile, just want to explain what I want...
    defaults: new { action = "Browse", new ComplexVM(departmentId) });
like image 318
Hooman Bahreini Avatar asked Dec 11 '18 06:12

Hooman Bahreini


2 Answers

I can achieve this with little change and with one trick

<a href="MyController/Browse?id=1">MenuItem1</a>

Controller action

public ActionResult Browse(ComplexVM complexVM)
{
return View(complexVM);
}

View model

public class ComplexVM
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; }
    public ComplexVM()
    {
        this.DepartmentId = System.Convert.ToInt32(HttpContext.Current.Request("id").ToString);
        this.DepartmentName = "Your name from cache"; // Get name from your cache 
    }
}

This is without using model binder. Trick may help.

like image 115
Pontus Carme Avatar answered Nov 08 '22 23:11

Pontus Carme


That is possible. It is also a good idea :) Off-loading parts of the shared responsibility to models / action filters is great. The only problem is because they are using some special classes to inherit from, testing them sometimes might be slightly harder then just testing the controller. Once you get the hang of it - it's better.

Your complex model should look like

// Your model class
[ModelBinder(typeof(ComplexVMModelBinder)]
public class ComplexVMModel
{
   [Required]
   public int DepartmentId { get; set; }

   public string DepartmentName { get; set; }
}

// Your binder class
public class ComplexVMModelBinder : IModelBinder
{
     // Returns false if you can't bind.
     public bool BindModel(HttpActionContext actionContext, ModelBindingContext modelContext)
     {
         if (modelContext.ModelType != typeof(ComplexVMModel))
         {
             return false;
         }

         // Somehow get the depid from the request - this might not work.
         int depId = HttpContext.Current.Request.Params["DepID"];
         // Create and assign the model.
         bindingContext.Model = new ComplexVMModel() { DepartmentName = CacheLookup(), DepId = depId };

         return true;
     }
}

Then at the beginning of your action method, you check the ModelState to see if it's valid or not. There are a few things which can make the model state non-valid (like not having a [Required] parameter.)

public ActionResult Browse(ComplexVM complexVM)
{
    if (!ModelState.IsValid)
    {
        //If not valid - return some error view.
    }
}

Now you just need to register this Model Binder.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    ModelBinders.Binders.Add(typeof(ComplexVMModel), new ComplexVMModelBinder());
}

Your should be able to use the route config that you've provided.

like image 38
Mavi Domates Avatar answered Nov 08 '22 23:11

Mavi Domates