NodaTime OffsetDateTime Binding in ASP.NET MVC - asp.net-mvc

I'm currently using NodaTime OffsetDateTimes in ASP.NET MVC to represent datetimes that I receive from an API.
In the front-end HTML I have a date picker control, which represents dates in the format dd/MM/yyyy.
Once the form containing the date in this format is posted to the server, the OffsetDateTime properties are null.
I've tried modifying the value prior to post, so that it matches the Rfc3339Pattern, but it still reaches the controller as null.
I've created the following custom Model Binder to make this work, and also cater for other instances where dates are passed in the Rfc3339Pattern:
public class OffsetDateTimeModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
// try Rfc3339Pattern format
var parseResult = OffsetDateTimePattern.Rfc3339Pattern.Parse(valueResult.AttemptedValue);
if (parseResult.Success)
{
return parseResult.Value;
}
// try dd/MM/yyyy format
parseResult = OffsetDateTimePattern.Create("dd/MM/yyyy", CultureInfo.CurrentCulture,
OffsetDateTime.FromDateTimeOffset(DateTimeOffset.MinValue)).Parse(valueResult.AttemptedValue);
if (parseResult.Success)
{
return parseResult.Value;
}
return null;
}
}
Is it necessary to create a custom ModelBinder for this, or is there something else I can do, similar to the NodaTime JSON.NET seralizer config?

Related

asp.net mvc fix (correcting) data before each action. Perform in Model or Controller?

I'm developing something like wizard with steps (controllers) and uses DerivedModel1, DerivedModel2,etc which is inherits from BaseModel and extends them with extra properties.
Models - only data, without business logic. All logic performed by services in controllers action for example _step1Service.GetRelated(model.id).
Now I want to not just validate Model (for this case there is ValidationAttribute) but fix invalid data in BaseModel:
public class BaseModel
{
public DateTime StartDate {get;set;}
}
StartDate should be greater than today. User can select invalid date and instead of validation error application should fix this value (reset to default?).
In my first attempt I added service for validating/correcting StartDate and call in each Action:
public ActionResult Index(DerivedModel1 model)
{
_svc.fixModel(model);
if(!ModelState.IsValid)
{
return View();
}
... do stuff with valid data
}
But don't like that, because have to add this line to each controller and action.
Then I add this correction to StartDate setter. It's looks better, but this breaks popular MVC paradigm that all logic should be in controller (or maybe i misunderstood something?)
I was thinking about possible solutions of this problem: ActionFilterAttribute, custom ModelBinder? But not sure is this right way and whether it work.
What you think about that?
you must implement IModelBinder to achieve this.
first define your custom model binder like this:
public class MyCustomModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Get the raw attempted value from the value provider
DateTime incomingDate = (DateTime) bindingContext.ValueProvider.GetValue("datefield").AttemptedValue;
//validate and correct date here ...
return new BaseModel{ DateMember = incomingDate };
}
}
then register your custom model binder such:
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof (BaseModel), new MyCustomModelBinder());
}
and your controller :
public ActionResult YourAction([ModelBinder(typeof(MyCustomModelBinder )] BaseModel model)
{
return Content("Ok");
}
There's a difference between validation and business rules. Objects can (and often should) be responsible to make sure they are in a valid state themselves.

How to return an overall Model's value as 'null' from ASP.NET MVC model binder

I have an action method that takes several optional parameters.
This ASP.NET MVC actionmethod looks simple enough but isn't working as I want....
[HttpPost]
public ActionResult UpdateOrder(OrderItem OrderItem, Address ShippingAddress)
{
if (ShippingAddress != null) {
// we have a shipping address
}
}
An Address object is always created for ShippingAddress because - well - thats the way model binders work. Even if ShippingAddress.Address1, ShippingAddress.City etc. fields are absent from the Form an object will still be created and passed to the action.
I want a way to make a model binder that returns null for the model if it is deemed to be empty.
A first attempt goes as follows
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
// get the address to validate
var address = (Address)bindingContext.Model;
// if the address is quintessentially null then return null for the model binder
if (address.Address1 == null && address.CountryCode == null && address.City == null)
{
bindingContext.Model = null;
}
}
Unfortunately this simple solution doesn't work and I get the following error:
InvalidOperationException -This property setter is obsolete, because its value is derived from ModelMetadata.Model now.
Is there a way I can make the overall 'Model' from a custom ModelBinder to return null?
Have you tried setting the default parameter to null? You may also need to set the type to nullable as well, but I'm not 100% sure if it's needed, but that's how I use it.
For example:
public ActionResult UpdateOrder(OrderItem OrderItem, Address? shippingAddress = null)
I should probably note that this requires .NET 4, but then, you didn't specify which version you're running on.

ASP.NET model binding to base type

I have a BaseViewModel that my View Models all inherit from.
public class MagazineViewModel : BaseOutputViewMode
{
public string TitleOfPublication { get; set; }
}
In my controller I use a factory method to give the corret View Model back based on an input:
// e.g. viewModel contains an instance of MagazineViewModel
BaseOutputViewModel viewModel = BaseOutputViewModel.GetOutputViewModel(output);
When I use TryUpdateModel to try and bind to a FormCollection which I know contains a "TitleOfPublication" key, its never set in my view model:
if (!TryUpdateModel(viewModel, form))
I think this is something to do with the DefaultModelBinder using the BaseOutputViewModel to bind FormCollection keys to - it doesn't contain a "TitleOfPublication", the derived MagazineViewModel does.
I'm trying to roll my own model binder, to override the DefaultModelBinder's BindModel behavior. Its all wired in correctly and I can debug into it straight after the TryUpdateModel call:
public class TestModelBinder : DefaultModelBinder, IFilteredModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Tried the following without success ....
// 1. Quick hardcoded test
// bindingContext.ModelType = typeof(MagazineViewModel);
// 2. Set ModelMetadata, hardcoded test again
// bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(MagazineViewModel));
// 3. Replace the entire context
// ModelBindingContext context2 = new ModelBindingContext();
// context2.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(MagazineViewModel));
// context2.ModelName = bindingContext.ModelName;
// context2.ModelState = bindingContext.ModelState;
// context2.ValueProvider = bindingContext.ValueProvider;
// bindingContext = context2;
}
}
But I'm not sure how to work with the bindingContext? What needs to be updated so that I can tell the DefaultModelBinder to bind using the derived View Model properties?
Or have I just totally mis-understood this!
I did try overriding CreateModel - much like the DerivedTypeModelBinder in MvcContrib, but I think because I'm giving the binder an instance of a model to work with, CreateModel is never called. Used Reflector on the Mvc DLL, theres a "BindComplexModel" that calls CreateModel only if the model is null:
if (model == null)
{
model = this.CreateModel(controllerContext, bindingContext, modelType);
}
Any pointers greatfully received!
Cheers
OK - finally got to the bottom of this!
In actual fact there was nothing wrong with my model binder, the problem ultimately led back to a couple of input tags that had no name/id:
<input id="" name="" type="text">
The crux was this test in DefaultModelBinder:
// Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string))
// or by seeing if a value in the request exactly matches the name of the model we're binding.
// Complex type = everything else.
if (!performedFallback) {
ValueProviderResult vpResult =
bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (vpResult != null) {
return BindSimpleModel(controllerContext, bindingContext, vpResult);
}
}
With no id/name, the form collection has a key of "" which means that the GetValue correctly returned the value for that field, continuing to bind as a simple model.
When an id/name are added, the form collection contains no key of "", (which is now the name of my model as we're using TryUpdateModel). This meant the DefaultModelBinder correctly treated my model as complexm successfully binding properties in my derived type!
Cheers

MVC binding form data problem

I am using an object that matches all the fields in my form. I then use the default binding to populate the object in my action, like this;
public ActionResult GetDivisionData(DivisionObj FormData)
My DivisionObj initializes all it's values to string.empty in the constructor.
The problem is that when the binder populates the model from the posted form data, any data that is not posted is set to null in the object, eventhough I initialized the object to contain empty strings.
Is there a way to change this so that unposted data will be an empty string.
This is default behavior of the DefaultModelBinder, more specifically the DataAnnotations framework. ConvertEmptyStringToNull is by default, set to true. You can create your own model binder and replace the default model binder.
public class EmptyStringModelBaseBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
return base.BindModel(controllerContext, bindingContext);
}
}
Then in global...
ModelBinders.Binders.DefaultBinder = new EmptyStringModelBaseBinder();
Though I do wish they had a static way of setting this for the default modelbinder. Maybe in v3 :)
Alternatively, You can also use the [DisplayFormat(ConvertEmptyStringToNull=false)] attribute to set this on a per-property basis.
You can always use [Bind(Exclude="PropertyName1,PropertyName2,PropertyName3")] to exclude some properties from binding:
public ActionResult GetDivisionData([Bind(Exclude="PropertyName1,PropertyName2,PropertyName3")]DivisionObj FormData)
If you really have to have String.Empty in String properties, you can use this binder:
public class EmptyStringModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
if (propertyDescriptor.PropertyType == typeof(String))
propertyDescriptor.SetValue(bindingContext.Model,propertyDescriptor.GetValue(bindingContext.Model) ?? String.Empty);
}
}
You'll also have to run this in global.asax:
ModelBinders.Binders.DefaultBinder = new EmptyStringModelBinder();
I can only confirm that I see the same results as you. Your options are:
One way is to exclude properties as LukLed explained. But this will lead to code duplication and you will have to do it on every controller action every time DivisionObj (or any other Model class that you wish to decorate) appears as action parameter. Soo it's a bit cumbersome...
I am currently in the process of having multiple issues with various custom properties, some that need to be instantiated in contructors, some that need value only known at runtime and others that are also special in some way.
I have determined that for me it's best to go with Custom Model Binder and do most of this stuff there.

Modelbinding database entities in ASPNET MVC

I'm having trouble trying to think what the best way is to recreate a database object in a controller Action.
I want to make use of ModelBinders so in my action I have access to the object via a parameter, rather than having to repeat code to get an object from the database based on an identifier parameter. So I was thinking of having a ModelBinder that performs a call to the dataaccess layer to obtain the original object (or creates a new one if it doesn't exist in the database), then binds any properties to the database object to update it. However I've read that the ModelBinders shouldn't make database queries (first comment of this article).
If the ModelBinder shouldn't perform a database query (so just using the DefaultModelBinder) then what about database objects that have properties that are other db objects? These would never get assigned.
Saving an object after the user has edited it (1 or 2 properties are editable in the view) the ModelBinded object would be missing data, so saving it as it is would result in data in the database being overwritten with invalid values, or NOT-NULL constraints failing.
So, whats the best way to get an object in a controller action from the database bound with the form data posted back from the view?
Note im using NHibernate.
I get the model object from the database, then use UpdateModel (or TryUpdateModel) on the object to update values from the form parameters.
public ActionResult Update( int id )
{
DataContext dc = new DataContext();
MyModel model = dc.MyModels.Where( m => m.ID == id ).SingleOrDefault();
string[] whitelist = new string[] { "Name", "Property1", "Property2" };
if (!TryUpdateModel( model, whitelist )) {
... model error handling...
return View("Edit");
}
ViewData.Model = model;
return View("Show");
}
Unfortunately you don't have control over the construction of the model binder, so you can't inject any repository implementation.
You can reach out directly into a service locator to pull in your repository & fetch the item:
public class ProductBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext, Type modelType)
{
if(modelType != typeof(Product))
return null;
var form = controllerContext.HttpContext.Request.Form;
int id = Int32.Parse(form["Id"]);
if(id == 0)
return base.CreateModel(controllerContext, bindingContext, modelType);
IProductRepository repository = ServiceLocator.Resolve<IProductRepository>();
return repository.Fetch(id);
}
}
You might even make this work for all of your entities if you can use a base class or interface that provides the Id of the class.
You'll have to set this up in Global.asax:
ModelBinders.Binders.Add(typeof(Product), new ProductBinder());
and then you can do this:
public ActionResult Save([Bind] Product product)
{
....
_repository.Save(product);
}
Let me first state that I don't recommend to access database from ModelBinders, as from perspective of Separation Of Concern ModelBinders should only be responsible of interpretting client request, obviously database is not.
If you dont want to repeat your self (DRY), use repositories/services
However if u really want to do it like that, then
In global.asax.cs Register a custom MyModelBinderProvider to MVC
ModelBinderProviders.BinderProviders.Add(new EntityModelBinderProvider
{
ConnectionString = "my connection string"
));
Cunstruct the custom ModelBinderProvider to contain database settings
public class EntityBinderProvider: IModelBinderProvider
{
public string ConnectionString { get; set; }
public IModelBinder GetBinder(Type modelType)
{
if (Is known entity)
return new EntityBinder(ConnectionString);
else
return null;
}
}
Follow further instructions from Ben Scheirman
You don't actually have to hit the database. Simply setting the Id of the objects will be enough to set the relationship up, but watch your cascades. Make sure your cascde settings won't update the related object as it will clear the values.

Resources