I have a few questions regarding custom model binding, model state, and data annotations.
1) Is it redundant to do validation in the custom model binder if I have data annotations on my model, because that's what I thought the point of data annotations were.
2) Why is my controller treating the model state as valid even when it's not, mainly I make the Name property null or too short.
3) Is it ok to think of custom model binders as constructor methods, because that's what they remind me of.
First here is my model.
public class Projects
{
[Key]
[Required]
public Guid ProjectGuid { get; set; }
[Required]
public string AccountName { get; set; }
[Required(ErrorMessage = "Project name required")]
[StringLength(128, ErrorMessage = "Project name cannot exceed 128 characters")]
[MinLength(3, ErrorMessage = "Project name must be at least 3 characters")]
public string Name { get; set; }
[Required]
public long TotalTime { get; set; }
}
Then I'm using a custom model binder to bind some properties of the model. Please don't mind that it's quick and dirty just trying to get it functioning and then refactoring it.
public class ProjectModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
var p = new Project();
p.ProjectGuid = System.Guid.NewGuid();
p.AccountName = controllerContext.HttpContext.User.Identity.Name;
p.Name = controllerContext.HttpContext.Request.Form.Get("Name");
p.TotalTime = 0;
//
// Is this redundant because of the data annotations?!?!
//
if (p.AccountName == null)
bindingContext.ModelState.AddModelError("Name", "Name is required");
if (p.AccountName.Length < 3)
bindingContext.ModelState.AddModelError("Name", "Minimum length is 3 characters");
if (p.AccountName.Length > 128)
bindingContext.ModelState.AddModelError("Name", "Maximum length is 128 characters");
return p;
}
}
Now my controller action.
[HttpPost]
public ActionResult CreateProject([ModelBinder(typeof(ProjectModelBinder))]Project project)
{
//
// For some reason the model state comes back as valid even when I force an error
//
if (!ModelState.IsValid)
return Content(Boolean.FalseString);
//_projectRepository.CreateProject(project);
return Content(Boolean.TrueString);
}
EDIT
I Found some code on another stackoverflow question but I'm not sure at which point I would inject the following values into this possible solution.
What I want to inject when a new object is created:
var p = new Project();
p.ProjectGuid = System.Guid.NewGuid();
p.AccountName = controllerContext.HttpContext.User.Identity.Name;
p.Name = controllerContext.HttpContext.Request.Form.Get("Name");
p.TotalTime = 0;
How do I get the above code into what's below (Possible solution):
public class ProjectModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(Project))
{
ModelBindingContext newBindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
() => new Project(), // construct a Project object,
typeof(Project) // using the Project metadata
),
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
// call the default model binder this new binding context
return base.BindModel(controllerContext, newBindingContext);
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
}
See Question&Answers more detail:os