I'm wondering if there is a good example of how to edit ASP.NET Profile settings in MVC using model binding.
Currently I have:
a custom ProfileCommon class derived from ProfileBase.
a strongly typed view (of type ProfileCommon)
get and post actions on the controller that work with ProfileCommon and the associated view. (see code below).
Viewing the profile details works - the form appears all the fields are correctly populated.
Saving the form however gives exception:System.Configuration.SettingsPropertyNotFoundException: The settings property 'FullName' was not found.
Thinking about this it makes sense because the model binding will be instantiating the ProfileCommon class itself instead of grabbing the one of the httpcontext. Also the save is probably redundant as I think the profile saves itself automatically when modified - an in the case, probably even if validation fails. Right?
Anyway, my current thought is that I probably need to create a separate Profile class for the model binding, but it seems a little redundant when I already have a very similar class.
Is there a good example for this around somewhere?
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Edit()
{
return View(HttpContext.Profile);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(ProfileCommon p)
{
if (ModelState.IsValid)
{
p.Save();
return RedirectToAction("Index", "Home");
}
else
{
return View(p);
}
}
It sounds correct when you say that the ProfileCommon instance is created from scratch (not from the HttpContext) in the post scenario - that's what the DefaultModelBinder does: it creates a new instance of the type based on its default constructor.
I think you could solve this issue by creating a custom IModelBinder that goes something like this:
public class ProfileBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
return controllerContext.HttpContext.Profile;
}
}
You may need to do some casting to make it fit your profile class.
To use this ProfileBinder, you could then add it to your Edit controller action like this:
public ActionResult Edit([ModelBinder(typeof(ProfileBinder))] ProfileCommon p)
Related
I'm trying to bind a form to a model parameter in a controller. The model contains a DateTime field and I'd like that to be bound when the form is submitted. The form field expects the date to be input in a non-standard format (which I can't change - for various reasons).
The controller action is:
public ActionResult Registration_Post(RegistrationDetails model)
I only need to custom bind a (DateTime) DateOfBirth property in the RegistrationDetails class. All the rest of the properties work with the default binding.
I don't want to override the DateTime binding for the whole app - just for this single action (or, if easier, controller).
Any idea how I can do this? I tried using the ModelBinder attribute on the action, as in:
public ActionResult Registration_Post([ModelBinder(typeof(CustomDateTimeBinder)]RegistrationDetails model)
However, it appears that I need to create a custom binder for the whole RegistrationDetails class, which seems like overkill.
Also, I'd prefer not to put the custom format on the model property, as the class is used elsewhere, so I be polluting the class.
I'm using MVC4.
Can anyone tell me the best way of handing this?
Try this: create a custom model binder provider.
In your BindModel method you will have to add logic to deal with the requirement that only the date of birth coming from your Registration_Post action has a special format. Btw, you need to bind the whole model.
using System;
using System.Web.Mvc;
using MvcApp.Models;
public class CustomModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
return modelType == typeof(Person) ? new PersonModelBinder() : null;
}
}
protected void Application_Start()
{
ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
}
public class PersonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext
bindingContext)
{
//add logic here to bind the person object
}
Is it safe to blindly overwrite the ModelMetaData of a binding context to set the type of the model? I'm worried the binder may be used to update pre-built models in edit scenarios and I will lose data if I just overwrite the ModelMetaData.
Sample code:
Public Class CustomModelBinder
Inherits DefaultModelBinder
Public Overrides Function BindModel(ByVal controllerContext As ControllerContext, ByVal bindingContext As ModelBindingContext) As Object
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(Nothing, GetSpecificModelTypeBasedOnBindingData(bindingContext))
Return MyBase.BindModel(controllerContext, bindingContext)
End Function
End Class
Yes, it is possible that the model will be already specified. This occurs, for example, when someone tries to update an existing model using TryUpdateModel/UpdateModel, as shown here.
public ActionResult Update(int id)
{
var modelToUpdate = GetExistingModel(id);
if (TryUpdateModel(modelToUpdate)) // replacing the Model or ModelBinderContext.Metadata in the model binder could have unexpected and unwanted results.
{
// etc.
}
// etc.
}
Currently I'm passing my domain objects to my views, and binding directly to them from POSTs. Everyone says this is bad, so I'm attempting to add in the ViewModel concept.
However, I can't find a way to do this very elegantly, and I'd like to know what other people's solutions are to not ending up with a very messy controller action.
the typical process for say some "add person" functionality looks like this:
make a GET request for a view representing a blank Person viewmodel
post back (in)valid data
controller binds posted data onto a person viewmodel
if binding fails, i need to do the same action as in (1) but with some data, not a blank object and errors
if the binding suceeded, i need to map the properties from the VM onto a real model
validate the model
if validation passed: save the person, commit, map the users details to a display VM and return it in a view
if validation failed, do the same actions as in (1) but with some data and errors
Doing all this in a controller action (ignoring the GET) certainly isnt SRP or DRY.
Im trying to think of a way of breaking this process up so that it does abide by SRP, is clean, modular and above all testable.
What are peoples solution to this?
I've been experimenting with custom controller-action-invokers to separate the concerns up into individual methods, smart modelbinders and just plain brute force but i havent yet come across a solution in happy with.
P.S. as it adds so much complexity, convince me why i even need to bother
I've felt the same discomfort. My only way around it has been to do the following:
Create a binder to bind and validate the view model
Create a binder to get the entity from the database (or just do this in the controller)
Call an inherited Save method in the superclass. This method takes the viewmodel and the entity that will be updated, and does all the work you listed in your steps.
The action method looks like this:
public ActionResult Whatever(TViewModel viewModel, TEntity entity)
{
return Save(viewModel, entity);
}
The base controller has a generic definition, like so:
public abstract BaseController<TEntity, TViewModel>
where TEntity : Entity
where TViewModel : ViewModel
The constructor has two dependencies, one for the entity repository and another for the model mapper, like so:
protected BaseController(IRepository<TEntity> repository, IMapper<TEntity, TViewModel> mapper)
With this in place, you can then write a protected Save method that can be called from the controller actions in the subclass, like so:
protected ActionResult Save(TViewModel viewModel, TEntity entity)
{
if (!ModelState.IsValid)
return View(viewModel);
_mapper.Map(viewModel, entity);
if (!entity.IsValid)
{
// add errors to model state
return View(viewModel);
}
try
{
_repository.Save(entity);
// either redirect with static url or add virtual method for defining redirect in subclass.
}
catch (Exception)
{
// do something here with the exception
return View(viewModel);
}
}
As far as testability, you can test the save method passing in valid/invalid view models and entities. You can test the implementation of the model mapper, the valid state of the view model, and the valid state of the entity separately.
By making the base controller generic, you can repeat this pattern for each entity/viewmodel combo in your domain, if you're creating many controllers to do the same thing.
I'm very interested to hear what others have to say about this. Great question.
The MVVM (ViewModel) pattern is definitely the one to go for, I had a similar question about POSTing back to an action a few days back - here is the link: MVVM and ModelBinders in the ASP.NET MVC Framework
The result was that you can use the Bind attribute to post back the complex type you want.
I have many good solutions in the asp.net mvc sample application which is in the download of valueinjecter (mapper that I use to map ViewModels to/from Entities, you can also map FormCollection/Request to Entities)
here's one:
public class TinyController :Controller
{
private readonly IModelBuilder<Person, PersonViewModel> modelBuilder;
public TinyController()
{
modelBuilder = new PersonModelBuilder();
}
public ActionResult Index()
{
return View(modelBuilder.BuildModel(new PersonRepository().Get()));
}
[HttpPost]
public ActionResult Index(PersonViewModel model)
{
if (!ModelState.IsValid)
return View(modelBuilder.RebuildModel(model));
var entity = modelBuilder.BuildEntity(model);
...
//save it or whatever
}
}
Here's a little background on my solution:
ASP.Net MVC app
Using Linq-to-SQL with table-per-hierarchy inheritance
Using DataAnnotationsModelBinder as default
So I have a Device abstract class and then a series of derived classes (ServerDevice, DiskDevice, PSUDevice, etc) that inherit from it in the proscribed Linq-to-SQL way. I have one controller that handles all these different related model types, and it renders different partials based on the type and a handy drop down to select them. My (GET) Create method looks like this:
// GET: /Devices/Create/3
public ActionResult Create(int? deviceTypeID)
{
return View(DeviceFactory(deviceTypeID);
}
Where DeviceFactory is a static method returns a new instance of one of the derived classes based on an int discriminator. The POST Create method looks like this:
// POST: /Devices/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([ModelBinder(typeof(DeviceModelBinder))]Device device)
{
if (!ModelState.IsValid)
return View(device);
_repository.Add(device);
_repository.Save();
TempData["message"] = string.Format("Device was created successfully.");
return RedirectToAction(Actions.Index);
}
and my custom model binder looks like this:
public class DeviceModelBinder : DataAnnotationsModelBinder
{
private readonly Dictionary<string, Type> _deviceTypes =
new Dictionary<string, Type>
{
{"1", typeof (ServerDevice)},
{"2", typeof (DiskDevice)}
// And on and on for each derived type
};
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext, Type modelType)
{
return base.CreateModel(controllerContext, bindingContext,
_deviceTypes[bindingContext.ValueProvider["deviceTypeID"].AttemptedValue]);
}
}
So after trying to hook this up all day, reading about ActionInvoker, custom ActionFilters, and all sorts of other MVC stuff, I'm wondering if the solution I've arrived at is a good one. Help allay my fears that I'm missing some hugely obvious concept and re-inventing the wheel. Is there a better or more concise way?
Thanks!
My POV is it's "smelly" to bind entity/domain types to a UI at all. I explained this in considerably more detail in this answer. IMHO you should nearly always use dedicated presentation models. It is a nice side benefit that model binding do a presentation model is considerably easier, but the more important benefits are discussed in the linked answer.
Let's say I have an interface like:
interface IThing {
int Id { get; set; }
string Title { get; set; }
}
And in ASP.NET MVC I have a form that posts to a controller action like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult NewThing([Bind(Exclude = "Id")] SimpleThing thing) {
// code to validate and persist the thing can go here
}
Where SimpleThing is a concrete class that just barely implements IThing.
However, I would like all my methods to deal with the interface. I have a data assembly that uses NHiberate and its own IThing implementation (let's call it RealThing). I can't pass the SimpleThing to it because it will complain about an "unknown entity".
Does anyone have any ideas about a cleaner way to do this? I was thinking about something along the lines of using a factory class. But how would I get the MVC form binder to use it?
Thanks!
You can use custom model binders. However article on msdn is totally useless. So better to employ search and find something better. There are planaty of articles available.
I came up with two approaches to this.
The first was to add code to my NHibernate Repository class to translate the simple POCO type used by the MVC controller (SimpleThing) to the type of entity that NHibernate wanted (RealThing):
/// <summary>
/// A NHibernate generic repository. Provides base of common
/// methods to retrieve and update data.
/// </summary>
/// <typeparam name="T">The base type to expose
/// repository methods for.</typeparam>
/// <typeparam name="K">The concrete type used by NHibernate</typeparam>
public class NHRepositoryBase<T, K>
: IRepository<T>
where T : class
where K : T, new()
{
// repository methods ...
/// <summary>
/// Return T item as a type of K, converting it if necessary
/// </summary>
protected static K GetKnownEntity(T item) {
if (typeof(T) != typeof(K)) {
K knownEntity = new K();
foreach (var prop in typeof(T).GetProperties()) {
object value = prop.GetValue(item, null);
prop.SetValue(knownEntity, value, null);
}
return knownEntity;
} else {
return (K)item;
}
}
So, any method in the repository can call GetKnownEntity(T item) and it will copy the properties of the item you pass in to the type that NHibernate wants. Obviously this felt a bit clunky, so I looked in to custom model binders.
In the second approach, I created a custom model binder like this:
public class FactoryModelBinder<T>
: DefaultModelBinder
where T : new()
{
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType) {
return new T();
}
}
Then I registered that in Global.asax.cs with:
ModelBinders.Binders.Add(typeof(IThing),
new FactoryModelBinder<RealThing>());
And it works fine with a Controller Action that looks like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult NewThing([Bind(Exclude = "Id")] IThing thing) {
// code to process the thing goes here
}
I like the second approach, but most of my dependency injection stuff is in the Controller class. I don't like to have to add all these ModelBinder mappings in Global.asax.cs.
There were some good suggestions here and I actually came up with a solution that works. However, I ended up with something altogether. I just created models specific for the form data I was posting and used the default model binder.
Much simpler and it allows me to capture data that isn't part of my domain model (ie. like a "comments" field).
This is not dirrect unswer to your question.
We use slightly different approach to deal with same problem you have. Our Controllers accepts DTOs that match persistent entity field by field. Then we user AutoMapper to create persisten entities that will go to the database. This eliminates unnessesery interfaces and locks down public facing API (means that renaming persistance object's field will not break our client code).