I have bindingContext.ValueProvider.GetValue(bindingContext.ModelName) for ModelBinding and it returns null, but if I use bindingContext.ValueProvider.GetValue("id") is returns the correct record. Any Idea what's missing? Am I supposed to register the model class somehow?
public class EntityModelBinder<TEntity>: IModelBinder where TEntity : Entity
{
private readonly IUnitOfWork unitOfWork;
public EntityModelBinder(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var id = Guid.Parse(value.AttemptedValue);
var entity = ((IGenericRepository<TEntity>)unitOfWork.GetRepository(typeof(TEntity))).GetByID(id);
return entity;
}
}
And Controller Call is "Bill" is one of my Entity Classes, and it's part of the UnitOfWork:
public ActionResult Edit(Bill bill)
{
var model = Mapper.Map<Bill, BillEditModel>(bill);
return View("Edit",model);
}
I am not an expert on mvc, but I have an idea about your issue. I am assuming that you are trying to get a Bill entity from your unit of work. Your action method defines the parameter Bill bill. This means that MVC will set bindingContext.ModelName to "bill", not "id".
Instead of trying to get Bill entity through model binding, I suggest using your unit of work within the controller. So the Edit action could be like
public ActionResult Edit(Guid id)
{
Bill bill = _unitOfWork.GetByID(id);
}
and your Controller constructor might be like:
public MyController(IUnitOfWork uow) {
_unitOfWork = uow;
}
This is assuming that you are using DI.
I think, using the model binder for getting entities from repository could be dangerous. Unless your GetByID method throws an exception, MVC will continue searching for Bill entity in request data. Imagine a scenario, where user posts a new entity that does not exist in your repository. Your controller will have to do some extra work to check whether this entity really exists in your repository.
You are better off using your unit of work within the controller.
Related
Background:
In my MVC post back action methods I am receiving command objects rather than view models. The idea is that these command objects (which roughly equate to transaction scripts) will be set up and ready to execute upon entering the action method, with the model binder having set parameters which are used during the execution process:
public class MyCommand : IMyCommand
{
// In this case Value1 and Value2 are being set by the default model binder from posted form values - wonderful :)
public String Value1 { get; set; }
public String Value2 { get; set; }
public CommandResult ExecuteCommand()
{
// Does something awesome...
}
}
To make things a little more complex, my command objects have dependencies (services, repositories etc) which are required in their respective constructors; so I had to create a custom model binder which used the default DependencyResolver (which was already set up with my IoC container) to construct the model objects:
public class DependencyModelBinder : DefaultModelBinder
{
protected override Object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return DependencyResolver.Current.GetService(modelType);
}
}
And set up in Global.asax.cs like so:
ModelBinders.Binders.DefaultBinder = new DependencyModelBinder();
Again this all works fine, the dependencies are injected into the constructor and then the default model binder takes over to set the properties as usual.
The Issue:
The problem I have is that all of my command objects have a 'SessionId' GUID parameter (which comes from a cookie), and the first thing they do is try to resolve a session object from this id using an injected service.
public class MyCommand : IMyCommand
{
public MyCommand (ISessionRepository sessionRepository) { ... }
public Guid SessionId { get; set; } // Set by model binder from a cookie...
public CommandResult Execute()
{
Session session = SessionRepository.Get(SessionId);
if (session == null)
// Do something not so awesome...
}
}
I wanted to remove this repetition, so I created a second model binder which would take care of this lookup in the repository, meaning my command objects could have a Session property directly (removing the constructor dependency for the session repository).
public class SessionModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var sessionRepository = DependencyResolver.Current.GetService<ISessionRepository>();
return sessionRepository.Get((Guid)controllerContext.HttpContext.Request["SessionId"]);
}
}
My Global.asax.cs file now looking like so:
ModelBinders.Binders.DefaultBinder = new DependencyModelBinder();
ModelBinders.Binders.Add(typeof(Session), new SessionModelBinder());
Having tested the SessionModelBinder in isolation, I know it works. However when using it in conjunction with the DependencyModelBinder, it is never called. How can I get MVC to use my DependencyModelBinder when constructing model objects, but have it use my SessionModelBinder when binding session properties on them? Or does anyone know a better approach to this?
You could use the GetPropertyValue method in your original model binder to provide a value for the Session property:
public class DependencyModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return DependencyResolver.Current.GetService(modelType);
}
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
if (propertyDescriptor.Name == "Session")
{
var sessionRepository = DependencyResolver.Current.GetService<ISessionRepository>();
return sessionRepository.Get(controllerContext.HttpContext.Request["SessionId"]);
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
I am designing an MVC 3 application where multiple tenants reside in a single database.
What is the best way to prevent users from editing/viewing other tenants data in MVC? (i.e. someone could type in '/People/Edit/1' and edit the person with Id of 1- regardless of wether they are part of the tenants data or not).
I know I can override 'OnActionExecuting(ActionExecutingContext filterContext)' for each controller- but it sounds crazy to have to handle each action seperately, get the ID or OBJECT depending on if its a POST or GET and then check if the operation is allowed.
Any better ideas?
Also, I do not want to go down the route of creating a different database or schema for each tenant.
Thanks in advance.
Instead of passing ids to your controller actions write a custom model binder for your entities which will fetch it from the database. So for example let's assume that you have the following model:
public class Person
{
public string Id { get; set; }
... some other properties
}
Now instead of having:
[HttpPost]
public ActionResult Edit(string id)
{
...
}
write:
[HttpPost]
public ActionResult Edit(Person person)
{
...
}
and then write a custom model binder for Person:
public class PersonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var id = bindingContext.ValueProvider.GetValue("id");
// check if an id was provided and if the user is authenticated
if (!controllerContext.HttpContext.User.Identity.IsAuthenticated || id == null)
{
throw new HttpException(403, "Forbidden");
}
var currentUser = controllerContext.HttpContext.User.Identity.Name;
// fetch the person from your repository given the id and belonging
// to the currently authenticated user
var person = _repository.GetPerson(id.AttemptedValue, currentUser);
if (person == null)
{
// no person found matching
throw new HttpException(403, "Forbidden");
}
return person;
}
}
which you would register in Application_Start:
ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder());
Original answer
To solve the problem quickly use guids instead of a auto-increasing integer. However this is just delaying the problem.
One of the things you can do is to role your own authorize attribuut http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.aspx Or you can chose to create a global actionfilter. http://www.asp.net/mvc/tutorials/understanding-action-filters-cs
Addition information on how you could do it based on request in comment
public class MySuperFilter : ActionFilterAttribute
{
//Called by the MVC framework before the action method executes.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
String user = filterContext.HttpContext.User.Identity.Name;
int id = int.Parse(filterContext.RouteData.GetRequiredString("Id"));
if (!IsValidUser(user,id))
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {{ "Controller", "YourController" },
{ "Action", "YourAction" } });
}
base.OnActionExecuting(filterContext);
}
private bool IsValidUser(string user,int id)
{
//Check if the user has acces to the page
return true;
}
}
It's a non-elegant solution but depending on scope it could be easiest. Having designed a similar system that has relatively few points of entry for multiple tenants data we just merely check that the CurrentUser is the Owner of the object that is being queried. Our use objects a common base interface that has the owner field so the check is made not carrying about the specific object but just from the interface. If there's a mismatch we throw a security exception and log that a user is probably playing around the query string seeing if they get our website to leak data the way many production websites do.
I'm looking to use the UpdateModel method to a Sub Class that retrieved at runtime, would be great if someone could shed the light on whether I'm making a total hash of it and/or whether or not what I'm trying to do is possible.
I'm using a generic action to control the validation of a bunch of partial views; I'm trying to get away from having a specific action per partial view.
Each partial view has a unique Model that derives from a Base Model:
public class ModelA : ModelBase{
[Required]
public string SomeStringProperty{get;set;}
...
}
public class ModelB : ModelBase{
[Required]
public DateTime? SomeDateProperty{get;set;}
...
}
public class ModelBase{
public Guid InstanceId{get;set;}
}
I'm using the FormCollection on the Action to get the submitted form elements and their values, this includes the type of model that the View should be using to validate its request. Ignore the security implications of this for this example, I'm aware of them and this is an internal only proof of concept
[HttpPost]
public ActionResult ChangeCaseState(int id, FormCollection formCollection)
{
Guid instanceId = new Guid(formCollection["instanceId"]);
string modelType = formCollection["modelType"];
//Return a specific Model class based on the event/modelType
var args = GetStateModelClass(modelType, instanceId);
try
{
UpdateModel(args);
if(Model.IsValid){
...
}
catch (Exception)
{
return View("~/Views/Shared/StateForms/" + modelType + ".ascx", args);
}...
And here is the code I'm using to return a Sub Class based on the modelType passed to the controller.
private static ModelBase StateModelClassFactory(string stateModelTypeName, Guid instanceId)
{
switch (stateModelTypeName)
{
case "modelTypeA":
return new ModelA(workflowInstanceId);
case "modelTypeB":
return new ModelB(workflowInstanceId);
...
}
Because the return type of the StateModelClassFactory method is of the Base Class, even though I'm actually returning a Sub Class, the Model Binder used by the UpdateModel method only binds against the values within the Base Class.
Any ideas on how I can solve this problem?
UPDATE:
I created a Customer Model Binder:
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
And Assigned the new Model Binder to the correct Base Class to see what was going on a little more under the hood:
ModelBinders.Binders.Add(typeof(ModelBase), new CaseController.CustomModelBinder());
When I debug the model binder and inspect the bindingContext, the Model property represets the correct Sub Class, but the ModelType property is that of the Base Class. Should I be looking at changing the ModelType within the BindModel method? If so any pointers on how to do this, the setter on the ModelType seems to have been made redundant. I also noticed that the SomeDateProperty from the Sub Class is actaully in the PropertyMetadata property....Seems so close to behaving as I'd like.
I just ran into this particular issue and found that a better general approach would be just to cast your model to dynamic while passing it into UpdateModel:
[HttpPost]
public ActionResult ChangeCaseState(int id, FormCollection formCollection)
{
...try
{
UpdateModel((dynamic)args);//!!notice cast to dynamic here
if(Model.IsValid){
...
}
catch...
This appears to set all available properties of my type, regardless of whether my variable is delcared with the base type.
There's a work item filed in CodePlex for this issue: http://aspnet.codeplex.com/workitem/8277?ProjectName=aspnet
So I think I've solved my problem. Basically because of the way that I'm retrieving the Model class before calling the UpdateModel, the Model Binder is binding the BaseClass even though the Model was that of the SubClass - this is the code I used to solve my particular problem:
public class SubClassModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = bindingContext.Model;
var metaDataType = ModelMetadataProviders.Current.GetMetadataForType(null, model.GetType());
bindingContext.ModelMetadata = metaDataType;
bindingContext.ModelMetadata.Model = model;
return base.BindModel(controllerContext, bindingContext);
}
}
And in the Global.asax:
ModelBinders.Binders.Add(typeof(ModelBase), new SubClassModelBinder ());
Thanks to Darin for his inital pointer.
To solve this problem you could write a custom model binder for the base type which based on the value of the string property will return the correct child instance.
I am trying to get UpdateModel to populate a model that is set as only an interface at compile-time. For example, I have:
// View Model
public class AccountViewModel {
public string Email { get; set; }
public IProfile Profile { get; set; }
}
// Interface
public interface IProfile {
// Empty
}
// Actual profile instance used
public class StandardProfile : IProfile {
public string FavoriteFood { get; set; }
public string FavoriteMusic { get; set; }
}
// Controller action
public ActionResult AddAccount(AccountViewModel viewModel) {
// viewModel is populated already
UpdateModel(viewModel.Profile, "Profile"); // This isn't working.
}
// Form
<form ... >
<input name='Email' />
<input name='Profile.FavoriteFood' />
<input name='Profile.FavoriteMusic' />
<button type='submit'></button>
</form>
Also note that I have a custom model binder that inherits from DefaultModelBinder being used that populates IProfile with an instance of StandardProfile in the overriden CreateModel method.
The problem is that FavoriteFood and FavoriteMusic are never populated. Any ideas? Ideally this would all be done in the model binder, but I'm not sure it is possible without writing a completely custom implementation.
Thanks, Brian
I would have to check the ASP.NET MVC code (DefaultModelBinder) but I'm guessing that its reflecting on the type IProfile, and not the instance, StandardProfile.
So it looks for any IProfile members it can try to bind, but its an empty interface, so it considers itself done.
You could try something like updating the BindingContext and changing the ModelType to StandardProfile and then calling
bindingContext.ModelType = typeof(StandardProfile);
IProfile profile = base.BindModel(controllerContext, bindingContext);
Anyways, having an empty Interface is weird~
Edit: just want to add that code above is just pseudo code, you would need to check DefaultModelBinder to see exactly what you want to write.
Edit#2:
Can you do:
public class ProfileModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
{
bindingContext.ModelType = typeof(StandardProfile);
return base.BindModel(controllerContext, bindingContext);
}
}
No need to make a model binder for AccountView, that one works fine.
Edit #3
Tested it out, the above binder works, just need to add:
ModelBinders.Binders[typeof(IProfile)] = new ProfileModelBinder();
Your action looks like:
public ActionResult AddAccount(AccountViewModel viewModel) {
// viewModel is fully populated, including profile, don't call UpdateModel
}
You can use IOC when setting the model binder (have the type constructor injected for instance).
Not inspecting the actual type behind the interface was discussed here: http://forums.asp.net/t/1348233.aspx
That said, I found a hackish way around the problem. Since I already had a custom model binder for this type, I was able to add some code to it to perform the binding for me. Here's what my model binder looks like now:
public class AccountViewModelModelBinder : DefaultModelBinder
{
private readonly IProfileViewModel profileViewModel;
private bool profileBound = false;
public AccountViewModelModelBinder(IProfileViewModel profileViewModel)
{
this.profileViewModel = profileViewModel;
}
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Bind the profile
if (profileBound)
return;
profileBound = true;
bindingContext.ModelType = profileViewModel.GetType();
bindingContext.Model = profileViewModel;
bindingContext.ModelName = "Profile";
BindModel(controllerContext, bindingContext);
}
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
{
var model = new AccountViewModel();
model.Profile = profileViewModel;
return model;
}
}
Basically, when the model binder is "done" binding the main AccountViewModel, I then alter the binding context (as suggested by eyston) and call BindModel once again. This then binds my profile. Note that I called GetType on the profileViewModel (which is supplied by the IOC container in the constructor). Also notice that I include a flag to indicate if the profile model has been bound already. Otherwise there would be an endless loop of OnModelUpdated being called.
I'm not saying this is pretty, but it does work well enough for my needs. I'd still love to hear about other suggestions.
I'm using asp.net mvc and I'm trying to create a new Employee, in my form I use the Html.DropDown(...) to display a list of Departments to select from.
Ideally I would like MVC to just figure out which Department was selected (Id property is the value in dropdown), fetch it and set it in the incoming Employee object. instead I get a null value and I have to fetch the Department myself using the Request.Form[...].
I saw an example here: http://blog.rodj.org/archive/2008/03/31/activerecord-the-asp.net-mvc-framework.aspx but that doesn't seem to work with asp.net mvc beta
This is basic CRUD with a well-proven ORM.... need it really be so hard?
ASP.NET MVC does not know how to translate the DepartmentId form value to a Department.Load(DepartmentId) call. To do this you need to implement a binder for your model.
[ActiveRecord("Employees")]
[ModelBinder(EmployeeBinder]
public class Employee : ActiveRecordBase<Employee>
{
[PrimaryKey]
public int EmployeeId
{
get;
set;
}
[BelongsTo(NotNull = true)]
public Department Department
{
get;
set;
}
// ...
}
The EmployeeBinder is responsible for turning route/form data into an object.
public class EmployeeBinder : IModelBinder
{
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// ...
if (controllerContext.HttpContext.Request.Form.AllKeys.Contains("DepartmentId"))
{
// The Department Id was passed, call find
employee.Department = Department.Find(Convert.ToInt32(controllerContext.HttpContext.Request.Form["DepartmentId"]));
}
// ...
}
#endregion
}
With this in place anytime an Employee is used as a parameter for an action the binder will be called.
public ActionResult Create(Employee employee)
{
// Do stuff with your bound and loaded employee object!
}
See This blog post for further information
I ported ARFetch to ASP.NET MVC a couple of weeks ago... maybe that could help you.