ASp.net MVC 2 changed how tryupdatemodel works, what is the easiest way to fix my code? - asp.net-mvc

I initialize my strings to blank, not null. Which worked fine in MVC 1, using tryupdatemodel in that any text entries that had no text in them would be set to a blank string, in MVC 2 RC2 apparently (at least from my testing) it sets them to null / nothing. So now I'm getting errors from my validation layer which requires those to be blanks not null. Another problem with it that I have found is that now via reflection it calls every property on my objects including the ones I've not specified in a bind include statement and even readonly properties that could not be set.
Any one have an idea of the easiest way to get around these problems without totally changing all my code? Or should I just suck it up and

About properties that you haven't bound - asp.net mvc 2 rc has this change from input validation to model validation, here is how it works now
I suppose the issue you have with string is also because of changes made in RC2, check out the release notes.

To fix the string defaulting to null instead of "" I had to do this first:
Public Class NullToEmptyStringModelBinder
Inherits DefaultModelBinder
Protected Overrides Sub SetProperty(ByVal controllerContext As System.Web.Mvc.ControllerContext, ByVal bindingContext As System.Web.Mvc.ModelBindingContext, ByVal propertyDescriptor As System.ComponentModel.PropertyDescriptor, ByVal value As Object)
If value Is Nothing AndAlso propertyDescriptor.PropertyType Is GetType(String) Then
value = ""
End If
MyBase.SetProperty(controllerContext, bindingContext, propertyDescriptor, value)
End Sub
End Class
And then add to the application start this:
ModelBinders.Binders.DefaultBinder = New NullToEmptyStringModelBinder

Related

Passing TimeSpan in Html.EditorFor()

I'm getting a very strange exception.
I have a model with a TimeSpan property and try to create a view.
public class Clock {
[DataType(DataType.Time)]
[DisplayFormat(DataFormatString = #"{0:hh\:mm}", ApplyFormatInEditMode = true)]
public TimeSpan Time {get;set;}
}
#Html.EditorFor(model => model.Time)
That is what I get
[InvalidOperationException: The model item passed into the dictionary is of type 'System.TimeSpan', but this dictionary requires a model item of type 'System.String'.]
System.Web.Mvc.ViewDataDictionary`1.SetModel(Object value) +321071
System.Web.Mvc.ViewDataDictionary..ctor(ViewDataDictionary dictionary) +377
System.Web.Mvc.WebViewPage`1.SetViewData(ViewDataDictionary viewData) +48
I've used this technique in another project and it works, but in my current project it fails and I don't know my. Maybe I've missed something or something is disabled.
When using #Html.EditorFor(), MVC first looks to see if it can find a template using the default convention (or one you may have defined in a custom ViewEngine).
If it can't find one -- and in your case, you didn't have one defined -- then it uses built in templates. In the case of TimeSpan, it was trying to use a template for a String type, which resulted in the exception you saw.
You will need to explicitly define a TimeSpan.cshtml template, typed with #model TimeSpan.
My answer is not so much an answer as a workaround, since I'm not sure of the reason for the exception, but for me it works if I do a clean checkout from TFS.

How to update custom ModelBinder to work with altered ModelBindingContext.ValueProvider interface in ASP.NET MVC RC2

I have a custom model binder that takes a comma separated list and cleans out any empty values, then passes it along to the default model binder. This worked in ASP.NET MVC Preview 2, but when I upgraded to RC2, the below won't compile because the interface of ValueProvider only has a GetValue() method, no [] accessor. Is what I'm doing below possible through some other mechanism in the binding context? I'd rather not have to create a full blown model binder for such a simple situation. The main goal is when the values are bound to a List<SomeEnum>, any empty values are skipped.
public class EnumListModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = bindingContext.ValueProvider[bindingContext.ModelName];
string[] rawValues = (string[])result.RawValue;
var newValues = new List<string>();
foreach (string value in rawValues)
{
if (!String.IsNullOrEmpty(value))
{
newValues.Add(value);
}
}
string newValuesAttempted = String.Join(",", newValues.ToArray());
// overwrite the ValueProviderResult with the cleaned up csv list
// this is the part I'm not sure how to implement using the interface
bindingContext.ValueProvider[bindingContext.ModelName] =
new ValueProviderResult(newValues.ToArray(), newValuesAttempted, result.Culture);
return System.Web.Mvc.ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
}
}
What, exactly, is wrong with using GetValue() instead of [] here? It does the same thing. But ValueProvider is an interface now, and interfaces can't have indexers. Hence, GetValue().
Change your code to:
var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
I'm a little surprised that the cast on the next line ever worked, though. Seems highly dependent on what the user actually submits and what the particular property type is. That's unrelated to your question, though.
There wasn't much of a solution here now that the ValueProvider collection is readonly. Instead I ended up using a custom model binder
Is there a way to have the DefaultModelBinder ignore empty items when binding to a List<Enum>

ASP.NET MVC Issue with Using Reflection Created Objects with the Default Model Binder

I am having a weird issue in ASP.NET MVC with objects not being updated with UpdateModel when passed a formCollection. UpdateModel does not appear to be working properly when the object being updated is created through reflection.
Scenario: I have an application which has approximately 50 lookup tables--each of which includes exactly the same schema including typical fields like id, title, description, isactive, and createdon. Rather than build 50 views, I wanted to have a single view which could display the data from all of the lookup tables. I created an Interface called IReferenceEntity and implemented it in each of the POCOs representing my lookup tables.
Using this interface, I am able to easily populate a view with a record from the lookup table. (I pass the items to the view via the following.)
System.Web.Mvc.ViewPage<MyNamespece.IReferenceEntity>
From the database to the view, every thing works perfectly.
However, when I attempt to update the model on post, I am running into some problems.
If I explicitly declare an object reference like the following, every thing works perfectly and the values of my object are updated with the values from my form. Hence, I can then update the database.
AccountStatus a = new AccountStatus();
UpdateModel(a, formCollection.ToValueProvider());
Unfortunately, hard coding the object type would completely defeat the reason for using an interface.
(A primary objective of the application is to be able to dynamically add new tables such as lookup tables without having to do anything "special". This is accomplished by reflecting on the loaded assemblies and locating any classes which implement a specific interface or base class)
My strategy is to determine the concrete type of the object at postback and then create an instance of the type through reflection. (The mechanism I use to determine type is somewhat primitive. I include it as a hidden field within the form. Better ideas are welcome.)
When I create an instance of the object using reflection through any of the following methods, none of the objects are being updated by UpdateModel.
Type t = {Magically Determined Type}
object b = Activator.CreatorInstance(t);
UpdateModel(b, formCollection.ToValueProvider());
Type t = {Magically Determined Type}
var c = Activator.CreatorInstance(t);
UpdateModel(c, formCollection.ToValueProvider());
Type t = {Magically Determined Type}
IReferenceEntity d = Activator.CreatorInstance(t);
UpdateModel(d, formCollection.ToValueProvider());
Note: I have verified that the objects which are being created through relection are all of the proper type.
Does anyone have any idea why this might be happening? I am somewhat stumped.
If I was really "hard up", I could create factory object which would many instantiate any one of these reference entity/lookup objects. However, this would break the application's ability to allow for new lookup tables to be added and discovered transparently and is just not quite as clean.
Also, I could try deriving from an actual ReferenceEntity base class as opposed to an interface, but I am doubtful whether this would make any difference. The issue appears to be with using reflection created objects in the modelbinder.
Any help is appreciated.
Anthony
Augi answered this on ASP.NET forums. It worked with only a couple of minor modifications. Thank you Augi.
The problem is that [Try]UpdateModel methods allow to specify model type using generic parameter only so they don't allow dynamic model type specification. I have created issue ticket for this.
You can see TryModelUpdate method implementation here. So it's not difficult to write own overload:
public virtual bool TryUpdateModelDynamic<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IDictionary<string, ValueProviderResult> valueProvider) where TModel : class
{
if (model == null)
{
throw new ArgumentNullException("model");
}
if (valueProvider == null)
{
throw new ArgumentNullException("valueProvider");
}
//Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);
IModelBinder binder = Binders.GetBinder( /*typeof(TModel)*/model.GetType());
ModelBindingContext bindingContext = new ModelBindingContext()
{
Model = model,
ModelName = prefix,
ModelState = ModelState,
//ModelType = typeof(TModel), // old
ModelType = model.GetType(),
// new
//PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
binder.BindModel(ControllerContext, bindingContext);
return ModelState.IsValid;
}
Does your IReferenceEntity contain setters on the properties as well as getters? I would think that the last sample would work if the interface had property setters, though you'd have to cast it to get it to compile.
Type t = {Magically Determined Type}
IReferenceEntity d = Activator.CreatorInstance(t) as IReferenceEntity;
UpdateModel(d, formCollection.ToValueProvider());
Normally the reason that it won't set a property on a class is because it can't find a public setter method available to use via reflection.
Just a quick "another thing to try":
UpdateModel(d as IReferenceEntity, formCollection.ToValueProvider());
Not sure if that will work, and I haven't tried it myself, but it's the first thing that came to mind.
If I get a chance later I'll peek at the Default Model Binder code and see if there's anything in there that is obvious...

Asp.Net MVC 1.0 custom Modelbinders - how to handle form posts and parameter names?

I have a custom model binding:
using System.Web.Mvc;
using MyProject.Model;
namespace MyProject.ModelBinders
{
public class VersionedIdModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//Not completely happy with this. What if the parameter was named something besides id?
return VersionedId.Parse(bindingContext.ValueProvider["id"].RawValue.ToString());
}
}
}
which works as long as the id is passed in the url (either explicitly or via a route definition.) However, if the Id is passed in a form as a hidden input field:
<input type="hidden" id="id" name="id" value="12a" />
Then ValueProvider["id"].RawValue is a string array, so the code below doesn't behave as expected.
In the controller code, I expect to simply be able to do:
public ActionResult MyAction(VersionedId id)
{
...
}
Two questions:
I am surprised that passing the id via form post causes the RawValue to be a string array. Is this the expected behavior, and is the "standard" way to handle this to check the type of the RawValue? I need to be able to handle both form posts and url routes.
Is it normal to check for the Name of the parameter in the model binder, or is there another way to do this whereby the controller action can use whatever parameter name it likes?
Regarding your question 1, there is some interesting discussion posted Custom ModelBinder and Release Candidate.
The RawValue property is what is
consumed by the ConvertTo() method,
while the AttemptedValue property is
what is consumed by the validation
system to provide an error message.
To see an example of this, break into
one of your action methods and inspect
the Controller.ValueProvider entries.
Some of the RawValue entries will be
strings (like those from Routing),
some of them will be string arrays
(like those from QueryString or Form),
etc. The reason for the
differentiation between RawValue and
AttemptedValue is that RawValue
supports passing array types to the
ConvertTo() method. We can't convert
to a multi-element array from a single
AttemptedValue, but we can convert to
a multi-element array from a RawValue
that is itself an array. This
supports the infrastructure to allow
binding to collection types.
I agree about the unfortunate naming, though unfortunately this isn't likely to change. :(
You could directly access
controllerContext.HttpContext.Request["id"]
and get the value from there. Should be a simple string in that case.

linq datacontext GetModifiedMembers in Attach scenario

I am trying to implement Optimistic Locking in an asp.net MVC application, as well as provide audit trailing.
The audit framework relies on being able to call DataContext.GetModifiedMembers during SubmitChanges, which makes good sense, i guess.
The optimistic locking uses ROWVERSION timestamps, serialized to base64 and put in a hidden field in the view.
My Edit action looks like this:
[AcceptVerb(HttpVerb.Post)]
public ActionResult Edit(MyType myType)
{
context.MyTypes.Attach(myType, true);
context.SubmitChanges(ConflictMode.FailOnFirstConflict);
}
When doing this, the DataContext.GetModifiedMembers will always return ALL the properties on MyType, rather than just the ones that are changed between the database and the values provided, which breaks the auditing.
Specifically it returns every property as having been changed from their new value to their new value, so its not even like I can do anything clever to the list.
I tried loading the object first, before attaching it, but this gives a duplicate key exception.
I then tried using UpdateModel, i.e.
[AcceptVerb(HttpVerb.Post)]
public ActionResult Edit(int id, FormCollection col)
{
var mt = context.MyTypes.Single( mt => mt.id = id);
UpdateModel(mt);
context.SubmitChanges(ConflictMode.FailOnFirstConflict);
}
This works with the auditing, but fails the optimistic locking.
Rather than a ChangeConflictException i get an InvalidOperationException because the UpdateModel is changing the concurrentTS field (which apparently is readonly).
What am I doing wrong?
Progress so far consists of doing the last part, and catching InvalidOperationException and looking for the text "Value of member 'ConcurrencyTimestamp'", and rethrowing that as a ChangeConflictException.
That seems to do the trick, but it is not pretty.

Resources