I am working with MVC3 and using a custom ModelBinder to pass around the ProducerListViewModel that I built.
Here is the controller code I am currently working with:
Function Filter(user As UserModel, viewModel As ProducerListViewModel) As ActionResult
If IsNothing(viewModel) Then
viewModel = New ProducerListViewModel(user)
End If
Return View(viewModel)
End Function
<HttpPost()> _
Function Filter(user As UserModel, viewModel As ProducerListViewModel, <Bind(Prefix:="Filter")> filterModel As ProducerFilterModel) As ActionResult
'update the filter in the view model and send off to the list method
viewModel.Filter = filterModel
Return RedirectToAction("List")
End Function
Function List(user As UserModel, viewModel As ProducerListViewModel) As ActionResult
Return Nothing
End Function
This is the code for the model that is bound in the custom ModelBinder
<Serializable()> _
<ModelBinder(GetType(ProducerListViewBinder))> _
Public Class ProducerListViewModel
<XmlIgnore()> _
Public Property Producers As IEnumerable(Of ProducerModel)
Public Property PagingInfo As New PagingInfoModel("Load More Producers")
Public Property Filter As New ProducerFilterModel()
Public Sub New(user As UserModel)
Me.Filter = ProducerFilterBL.Retrieve(user)
End Sub
End Class
Here is the code for the Binder:
Public Class ProducerListViewBinder
Implements IModelBinder
Private Const __sessionKey As String = "ProducerListView"
Public Function BindModel(controllerContext As ControllerContext, bindingContext As ModelBindingContext) As Object Implements IModelBinder.BindModel
Dim filter As ProducerListViewModel
'check to see if the filter exists in session
If IsNothing(controllerContext.HttpContext.Session(ProducerListViewBinder.__sessionKey)) Then
'load existing filter for user and store for later retrieval
Dim user As UserModel = UserBL.RetrieveUser()
filter = New ProducerListViewModel(user)
ProducerListViewBinder.SetItem(filter)
Else
filter = CType(controllerContext.HttpContext.Session(ProducerListViewBinder.__sessionKey), ProducerListViewModel)
End If
Return filter
End Function
End Class
The flow goes like this:
User navigates to filter page, hitting the first action (Filter)
User makes changes to the filter and submits the page, hitting the second action (Filter w/ Post)
The filter w/ Post action receives the updated filter using model binding (third parameter filterModel As ProducerFilterModel), updates the ProducerListFilterModel, and redirects to the List action
Everything works just fine, but here is my question:
Why does the List action have the updated version of the ProducerFilterModel inside of the ProducerListViewModel?
I love that it works so perfectly, I just want to know why it works.
Now that you have shown the code for your model binder everything is very clear. This model binder stores the ProducerListViewModel instance into the session which is what allows it to survive the redirect.
The first time the POST Filter action is hit, there's nothing in the session, so your custom model binder does some database lookup or something to retrieve the value:
Dim user As UserModel = UserBL.RetrieveUser()
filter = New ProducerListViewModel(user)
ProducerListViewBinder.SetItem(filter)
and then stores this value into the session. I guess it is the ProducerListViewBinder.SetItem that does this job. Unfortunately you haven't shown the code of it but I am ready to bet 5 bucks that it's what it do.
Then the Filter action executes, and at the end it redirects to the List action which takes a ProducerListViewModel as argument. So your custom model binder kicks in again but this time it finds the instance it previously stored into the session and it simply returns it from there.
So there's no magic here. It simply uses the ASP.NET session in order to persist the values between the redirects.
Related
My MVC action method receives an entity object (Page) that the default model binder creates from form collection data. Some of the fields are wrong or null because they were not sent in the request to the server, for example I do not send "CreateDate" and the default model binder sets this property to some default value which I don't need.
Once the object is attached it of course tries to persist all the values (including invalid/not needed ones to the database). I could of course assign manually on a per property basis but was wondering if maybe I can somehow flag a property so it is not persisted when EntityState is set to modified and SaveChanges() is called..
public ActionResult SomeMethod(Page page)
{
page.ModifyDate = DateTime.Now;
_db.NewsPages.Attach(page);
_db.ObjectStateManager.ChangeObjectState(page, System.Data.EntityState.Modified);
_db.SaveChanges();
_db.Dispose();
}
The correct way to handle this is using different class for view model, attach empty entity to the context and assign real values per property (or let AutoMapper to handle this scenario) as #Darin suggested in the comment.
If you want to go your way you must not change state of the POCO entity but you must change state of every changed property:
public ActionResult SomeMethod(Page page)
{
page.ModifyDate = DateTime.Now;
_db.NewsPages.Attach(page);
ObjectStateEntry entry = _db.ObjectStateManager.GetObjectStateEntry(page);
entry.SetModifiedProperty("ChangedPropertyName");
// Do the same for all other changed properties
_db.SaveChanges();
_db.Dispose();
}
I have a field that may or may not be require based on user preferences which are stored in the database. To handle this I have created a custom validation attribute but I'm not sure how to check to see if the field is actually required based on the user preferences.
I tried setting an "IsRequired" property on my view model via the controller & checking that value in the custom attribute however the property is always false since the validation is firing before the property can be set.
Using data annotations how can I get the "IsRequired" property set before/when the validation kicks off? Should I be checking to see if the field is required once it is already passed to the controller instead of using data annotations?
ViewModel:
Public Class MyViewModel
<MyCustomValidationAttribute("IsMyFieldRequired")>
Public Property MyFieldThatMayOrMayNotBeRequired As String
Public Property IsMyFieldRequired As Boolean
Public Sub New(objectUsedToSetIsMyFieldRequired As UserPreferences)
'Set IsMyFieldRequired based on passed in user preferences
End Sub
End Class
Custom Validation Attribute:
Public Class MyCustomValidationAttribute
Inherits ValidationAttribute
Private _otherPropertyName As String
Public Sub New(otherPropertyName As String)
Me._otherPropertyName = otherPropertyName
End Sub
Protected Overrides Function IsValid(value As Object, validationContext As System.ComponentModel.DataAnnotations.ValidationContext) As System.ComponentModel.DataAnnotations.ValidationResult
Dim basePropertyInfo As System.Reflection.PropertyInfo = validationContext.ObjectType.GetProperty(_otherPropertyName)
Dim isRequired As Boolean = Not CBool(basePropertyInfo.GetValue(validationContext.ObjectInstance, Nothing))
'
If isRequired AndAlso value Is Nothing Then Return New ValidationResult(Me.ErrorMessage)
'
Return Nothing
End Function
End Class
You shouldn't be doing any database access in your validation. You could use remote validation and call an action method to lookup the value, or you could have a property in your model that you set. You can use a variation of the "comparison" custom validation that many people have created. Here's an example http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
Is there a way that I can invoke the model binder for a single object?
I don't want/need a custom model binder - I just want to do something like this:
MyViewModel1 vModel1 = new MyViewModel1();
InvokeModelBinder(vModel1);
MyViewModel2 vModel2= new MyViewModel2();
InvokeModelBinder(vModel2);
And when I'm done, the properties of both vModel1 and vModel2 have been bound to what's in the incoming request. Because of the way that our controller/action is being written, I don't necessarily want to list vModel1 and vModel2 in the action method's input list (since there will end up being a potentially long list of view models to optionally bind against).
Use Controller.UpdateModel:
MyViewModel1 vModel1 = new MyViewModel1();
UpdateModel(vModel1);
Update
Note if ModelState in controller has validation errors (related to model passed in action), UpdateModel (with any model) throws excetion, despite UpdateModel success and vModel1 is updated. Therefore errors in ModelState should be removed, or put UpdateModel in try/catch and just ignore excetion
This is wrong on many levels IMHO:
This is not how ASP.NET MVC is designed to work.
Your actions do not define a clear contract of what data they expect.
What do you get out of it? Smells like bad design.
Model binding is driven by reflection. Before an action is invoked it will reflect the method parameters list and for each object and its properties it will invoke a model binder to find a value for each property from the various value providers (form POST values provider, url parameters, etc). During model binding the ModelState validation is done as well.
So by not using the default ASP.NET MVC to do this you are losing all that.
Even if you were to manually get hold of a model binder like that:
IModelBinder modelBinder = ModelBinders.Binders.GetBinder(typeof(MyObject));
MyObject myObject = (MyObject ) modelBinder.BindModel(this.ControllerContext, ** ModelBindingContext HERE**);
You can see that you need to initalize a ModelBindingContext, something that ASP.NET MVC will do internally based on the current property it is reflecting. Here is the snipped from the ASP.NET MVC source code:
protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) {
// collect all of the necessary binding properties
Type parameterType = parameterDescriptor.ParameterType;
IModelBinder binder = GetModelBinder(parameterDescriptor);
IDictionary<string, ValueProviderResult> valueProvider = controllerContext.Controller.ValueProvider;
string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
// finally, call into the binder
ModelBindingContext bindingContext = new ModelBindingContext() {
FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
ModelName = parameterName,
ModelState = controllerContext.Controller.ViewData.ModelState,
ModelType = parameterType,
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
object result = binder.BindModel(controllerContext, bindingContext);
return result;
}
I was researching something and came across this blog post at buildstarted.com about model binders. It actually works pretty darn well for my purposes but I am not sure exactly whats going on behind the scenes. What I did was create a custom ModelBinder called USerModelBinder:
public class UserModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult value = bindingContext.ValueProvider.GetValue("id");
MyEntities db = new MyEntities();
User user = db.Users.SingleOrDefault(u => u.UserName == value.AttemptedValue);
return user;
}
}
Then in my Global.asax.cs I have:
ModelBinders.Binders.Add(typeof(User), new UserModelBinder());
My understanding is that using the model binder allows me to NOT have to use the following lines in every controller action that involves a "User". So instead of passing in an "id" to the action, the modelbinder intercepts the id, fetches the correct "item"(User in my case) and forwards it to the action for processing.
MyEntities db = new MyEntities();
User user = db.Users.SingleOrDefault(u => u.UserName == value.AttemptedValue);
I also tried using an annotation on my User class instead of using the line in Global.asax.cs:
[ModelBinder(typeof(UserModelBinder))]
public partial class User
{
}
I'm not looking for a 30 page white paper but I have no idea how the model binder does what it does. I just want to understand what happens from when a request is made to the time it is served. All this stuff "just working" is not acceptable to me, lol. Also, is there any difference between using the annotation versus adding it in Global.asax.cs? They seem to work the same in my testing but are there any gotchas?
Usually the Model Binder (in MVC) looks at you Action method and sees what it requires (as in, the objects types). It then tries to find the values from the HTTP Request (values in the HTTP Form, QueryString, Json and maybe other places such as cookies etc. using ValueProviders). It then creates a new object with the parameters that it retrieves.
IMO What you've done is not really "model binding". In the sense that you've just read the id and fetched the object from the DB.
example of usual model binding:
// class
public class SomeClass
{
public int PropA {get;set;}
public string PropB {get;set;}
}
// action
public ActionResult AddSomeClass(SomeClass classToBind)
{
// implementation
}
// pseudo html
<form action="">
<input name="PropA" type="text" />
<input name="PropB" type="text" />
</form>
if you post a form that contains the correct values (lets say you post a form with PropA and PropB ) the model binder can identify that you've sent those values in the form and build a SomeClass object.
If you really want to create a real working example you should use a strongly typed View and use HtmlHelper's EditorFor (or EditorForModel) to create all the correct names that MVC needs.
--
for reference MVC's default binder is the DefaultModelBinder, and some (there are more, you can look around in the System.Web.Mvc namespace) ValueProviders that it uses by default are the FormValueProvider and the QueryStringValueProvider
So, as I already said, how this basically works is that the default model binder reads the model that the action is recieving (say SomeClass in the example) reads what are the values that it can read (say PropA and PropB) and asks the ValueProviders for the correct values for the properties.
Also, if I recall correctly, you can also see the value providers in runtime using the ValueProviderFactories static class.
A ModelBinder looks at the arguments of the selected Controller action's method signature, then converts the values from the ValueProviders into those arguments.
This happens when the ControllerActionInvoker invokes the action associated with the ControllerContext, because the Controller's Execute() method told it to.
For more about the ASP.NET MVC execution process, see Understanding the MVC Application Execution Process
I have an action method that takes in a string as its only parameter. The action method transforms it, and returns the result back to the client (this is done on an ajax call). I need to allow markup in the string value. In the past, I've done this by decorating the property on my model with [AllowHtml], but that attribute cannot be used on a parameter and the AllowHtmlAttribute class is sealed, so I cannot inherit from it. I currently have a work around where I've created a model with just one property and decorated it with the aforementioned attribute, and this is working.
I don't think I should have to jump through that hoop. Is there something I'm missing, or should I make a request to the MVC team to allow this attribute to be used on method parameters?
If you need to allow html input for a particular parameter (opposed to "model property") there's no built-in way to do that because [AllowHtml] only works with models. But you can easily achieve this using a custom model binder:
public ActionResult AddBlogPost(int id, [ModelBinder(typeof(AllowHtmlBinder))] string html)
{
//...
}
The AllowHtmlBinder code:
public class AllowHtmlBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var name = bindingContext.ModelName;
return request.Unvalidated[name]; //magic happens here
}
}
Find the complete source code and the explanation in my blog post: https://www.jitbit.com/alexblog/273-aspnet-mvc-allowing-html-for-particular-action-parameters/
Have you looked at ValidateInputAttribute? More info here: http://blogs.msdn.com/b/marcinon/archive/2010/11/09/mvc3-granular-request-validation-update.aspx.