简体   繁体   中英

Filling entity in model binder vs external method that replace binder

I have asp.net MVC web application. It is the list of some entities where a user could pick up some of them and views details and so on.

Most of the actions of this application have some Entity as a parameter. For example:

[Authorize]
public ActionResult Details(Entity entity)
{
    ...
    return View();
}

I keep this entity in session and fill this parameter in the model binder by id from URL. It looks like:

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    var httpContext = controllerContext.HttpContext;
    var values = httpContext.Request.RequestContext.RouteData.Values;

    if (!values.ContainsKey("id")) return null;

    var id = values["id"].ToString();
    var obj = httpContext.Session["Entities"];
    var list =  (List<Entity>) obj;
    return list?.SingleOrDefault(x => x.Id == id);
}

But time to time it is possible case that the user tries to access to action when session object is empty. For example, a user gets URL by email to action and omits code that puts Entity in session.

What is the best fix for this case?

1. Implement getting entity in the binder:

    var id = values["id"].ToString();
    var obj = httpContext.Session["Entities"];
    var list =  (List<Entity>) obj;
    var entity = list?.SingleOrDefault(x => x.Id == id);

    if(null==entity){
        entity = repository.GetEntity(id);
        //set entity in session
    }

    return entity; 

This way looks nasty because of I have to implement extra filling logic in the binder. Also, I can't inject repository in the binder. But this way is simple and easy to implement.

2. Remove the binder and add some get method:

Remove binder at all and change all action with Entity in the next way:

[Authorize]
public ActionResult Details()
{
    Entity entity = GetEntity();
    ...
    return View();
}
private Entity GetEntity()
{
    var id = ... //get id from http request.
    var list = (List<Entity>)Session["Entities"];
    var entity = list?.SingleOrDefault(x => x.Id == id);

    if(null==entity){
        entity = repository.GetEntity(id);
        //set entity in session
    }

    return entity; 
}

I can implement injection of repository now, the code is safe but looks ugly.

Which solution is best? Is it possible that I have missed any another implementation?

This way looks nasty because of I have to implement extra filling logic in the binder. Also, I can't inject repository in the binder.

You are correct that dependency injection cannot be properly done in a model binder. But you could use a factory:

public class MyModelBinder : DefaultModelBinder
{
    private readonly Func<IRepository> repositoryProvider;

    public MyModelBinder(Func<IRepository> repositoryProvider)
    {
        this.repositoryProvider = repositoryProvider;
    }

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var id = ... //get id from http request.
        var list = (List<Entity>)Session["Entities"];
        var entity = list?.SingleOrDefault(x => x.Id == id);

        if (null == entity)
        {
            IRepository repository = this.repositoryProvider();
            entity = repository.GetEntity(id);
            //set entity in session
        }

        return entity; 
    }
}

and then when registering your binder:

ModelBinders.Binders[typeof(Entity)] = new MyModelBinder(
    () => DependencyResolver.Current.GetService<IRepository>()
);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM