I'm developing a wizard type solution in MVC2 and I'd like to prevent users from going to step 2 directly, however, I'd still like it to show up in the URL.
Also, because progress can be saved at any time, I'd still like the ability to programmatically go to Step2. How can I do this?
[HttpGet]
public ActionResult Step1() {
return View("Step1View");
}
[HttpPost]
public ActionResult Step1(Stuff s) {
return RedirectToAction("Step2", new { S = s });
}
[HttpGet] //<-- how do I stop users going directly here
public ActionResult Step2(Stuff s) {
return View();
}
[HttpPost]
public ActionResult Step2(Stuff2 s) {
return RedirectToAction("Step3");
}
I haven't tried this myself but if I were to I'd give some consideration to ActionFilters. I'd create some context object that describes the wizard data and the steps through that wizard (maybe wrapping a model or two in some fashion).
This wizard context would be self 'validating' in the sense that I can ask what the next valid step is.
Then, with that, I load it up from the action filter and then if the current action is not valid for that step I redirect.
Of course, I can do that without the action filter and just have that as pre-amble to the method I'm looking at. Personally, I'd do this first, then play with action filters to try and make it look a little neater if I had time.
Action filters are the way to go
First of all this will heavily depend on the storage system of your temporary saved data. Action filter should check this store and see whether step 1 data exists. If it doesn't you can always add an error to ModelState thus make it invalid. Your Step 2 code would therefore look like this:
[HttpGet]
[CheckExistingData]
public ActionResult Step2(Stuff s)
{
if (!this.ModelState.IsValid)
{
return RedirectToAction("Step1");
}
return View(s);
}
So. Your filter should check for existing data and either:
fill up Stuff parameter or
add model state error and keep parameter null
Data consolidation
The way that you've written redirect to action (in step 1 POST) to just provide a complex object is not a good way of doing it. You should consolidate data storage, so no matter where your user came to step 2, filter would always work the same. In your Step 1 POST you should save data to this particular storage and just redirect to Step 2.
You have two scenarios of getting to Step 2:
From step 1
From anywhere after data has been saved
This way, your step 2 would work the same for both scenarios. And you could create an arbitrary number of steps in your wizard, and the same process would still work the same.
I have finished developing a highly reusable wizard that by just doing:
return Navigate();
from the actions, the wizard knows what to do (this is possible if you implement a wizard pattern). Navigate() being a method defined on a base WizardController class.
The reason this works is that, in essence, step info gets serialized to the page with each request (AJAX or not), and is deserialized when the controller reads the response in the OnActionExecuting method.
The framework uses WizardStep attributes to know which action corresponds to which wizard step, and the controller is decorated with a WizardOptions attribute that dictates how the Wizard will allow itself to be navigated. EG:
[WizardStepOptions(WizardNavigatorRules.LeapBackOnly, WizardButtonRules.Both, WizardCompleteRules.DisableNavigation)]
public class MembershipFormController : WizardController<ESregister.Models.TheSociety.RegistrationData>
{
[WizardStep(1, "Start")]
public override ActionResult Start()
{
return Navigate();
}
It works a dream. If in the course of your wizard's use, you need to prune or add steps, you just define which steps are to be displayed using the Range property, also defined on the base class WizardController:
[WizardStep(2, "Category")]
public ActionResult Category()
{
return Navigate();
}
[HttpPost]
public ActionResult Category(int ? Category)
{
if (Category == null)
{
ModelState.AddModelError("Category", "You must fill in a Category!");
return Navigate();
}
if (Category == 3)
{
Range = new List<int> { 1, 2, 7, 8 };
}
else
{
Range = DefaultRange();
}
return Navigate();
}
The wizard framework implements PRG automatically. You only need to provide HttpPost in a case like the above where you need to, for example, prune the steps range depending on user input.
It also provides navigation controls as follows:
<% StepManager stepManager = (StepManager)TempData["stepManager"];
Html.WizardNavigator(stepManager); %>
Html.WizardButtons(stepManager, WizardButtonLocation.Top); %>
Where the WizardNavigator shows / provides links to the different steps (links if allowed) and WizardButtons are the Start, Next, Continue, Previous and Confirm buttons.
It is working in production.
I have included all this detail to show what is possible and that the suggested solution does work.
Related
I have a view model like such:
public class MyViewModel
{
public string Name { get; set; }
public List<Purchases> Purchases { get; set; }
}
This viewmodel is sent to a view that allows the user to edit the name property. The Purchases property is used only to create a dropdown box for it:
<%: Html.DropDownListFor(t => t.Name, new SelectList(Model.Purchases, "Value", "Text")) %></p>
This works fine.
However, when I perform server-side validation and then return to the View, I'm getting an object null reference error because the Purchases property is now set to null. I'm guessing this is because when the form is submitted because the Purchases property isn't bound to any editable control, it isn't being passed back with the viewmodel.
How can I prevent this happening? I want to send back the List to be send back with the Post request always.
You don't need to send back the list. If validation fails then simply rebuild the view model from scratch. One of the main selling points of MVC is how well it works in a stateless environment. Web Forms used ViewState to do this kind of thing, I don't think you want to replicate this kind of functionality though.
I like to have two overloaded Action methods for this (both with the same name but different method signatures). One with an [HttpGet()] attribute and the other with an [HttpPost()]. If your model is found to be invalid on the POST then simply return the "GET" method (NOTE: you'll need to to pass in any parameters required to rebuild the view).
When I say return, I mean:
return MyGetAction();
and not a Redirect to the GET action.
If the model is valid then you could/should perform a RedirectToAction() to a GET Action (this means if the user hits the refresh button it won't submit the form again, this is called the Post/Redirect/Get (PRG) pattern)
You'd have to create a hidden input for each of the elements in the list in addition to the select list. Having said, that I think caching the results of the query on the server is a better way to handle repopulating the list if you don't want to perform the query again. There's no sense in sending the data back across the wire if the server can just hang on to it. Personally, I wouldn't even bother with the caching unless it proved to be a performance bottleneck. Just populate the model selection list from the DB.
<% for (int i = 0; i < Model.Purchases.Length; ++i) { %>
<%: Html.Hidden( string.Format( "Purchases[{0}]", i ), Model.Purchases[i] ) %>
<% } %>
Lee Gunn is spot on. To make the answer a little more concrete, it is very common to re-build non-scalar values in the event ModelState is not valid. This can be done by simply returning the [HttpGet] version in your Controller. However, you could simply re-build the purchases collection manually. It's up to you.
[HttpGet]
public ActionResult MyView(string name)
{
//get entity and build up a view model
var entity = _myDb.GetEntity(name);
MyViewModel vm = AutoMapper.Map<Entity, MyViewModel>(entity);
return vm;
}
[HttpPost]
public ActionResult MyView(MyViewModel vm)
{
If(!ModelState.IsValid)
{
//here is one way of doing it
return MyView("");
//OR
vm.Purchases = GetSomePurchasesBro();
return View(vm);
}
//continue on persisting and doing the Post Redirect Get
}
P.S.
Calling
return MyView("");
can be replaced with
return MyView(vm.Name);
Both will do the same thing (provided you're using the Html Helper Extensions, i.e. Html.TextBoxFor(x=>x.Name))
Defaut model binding looks in the ModelState for attemptedValues when rendering Html. This is described here by Steve Sanderson.
Let's say I have a Controller that handles a CRUD scenario for a 'Home'. The Get would look something like this:
[HttpGet]
public ActionResult Index(int? homeId)
{
Home home = homeRepo.GetHome(homeId.Value);
return Json(home, JsonRequestBehavior.AllowGet);
}
So far so good. Then I add a post action for adding new ones.
[HttpPost]
public ActionResult Index(Home home)
{
//add the new home to the db
return Json(new { success = true });
}
Awesome. But when I use the same scheme to handle puts (updating an existing home)...
[HttpPut]
public ActionResult Index(Home home)
{
//update existing home in the db
return Json(new { success = true });
}
We run into a problem. The method signatures for Post and Put are identical, which of course C# doesn't like. I could try a few things, like adding bogus parameters to the signature, or changing the method names to directly reflect CRUD. Those are hacky or undesirable, though.
What is the best practice for going about preserving RESTful, CRUD style controllers here?
This is the best solution that I know of:
[HttpPut]
[ActionName("Index")]
public ActionResult IndexPut(Home home)
{
...
}
Basically the ActionNameAttribute was created to deal with these scenarios.
HttpPut and HttpDeletes are restricted by some firewalls so at times simply HttpPost and HttpGet are used. If a record ID is passed in (or some other criteria) you know its an update. Granted - this is for you to determine, httpput may work just fine for you, this is just a warning on it, it usually isn't a big deal.
Either method used - beware of users trying to inject false IDs into the page in order to forcing updates of records they don't have access to. I get around this issue by hashing in this case home.HomeId on the view when we render it
ViewData["IdCheck"] = Encryption.ComputeHash(home.HomeId.ToString());
in your view:
<%: Html.Hidden("IdCheck", ViewData["IdCheck"]) %>
in your HttpPost or HttpPut method (whichever is doing the update)
if (Encryption.ComputeHash(home.HomeId.ToString()) != (string)Request.Form["IdCheck"])
{
throw new Exception("Hashes do not match");
}
Again - this same security issue exists no matter which method you use to do your update if you are trusting form data.
Suppose I have this model:
public class ViewModel
{
[Required]
public string UserInput { get; set; }
[Required]
public Guid EntityId { get; set; }
}
Now, when UserInput is wrong, I want to re-display the same page but with validation errors (e.g. /Edit). However, when EntityId is wrong, I want to redirect to some other page (e.g. /Create).
I can do this manually inside each controller...
if (!ModelState.IsValidField("EntityId")) { redirect }
//or
if (string.IsNullOrEmpty(data.EntityId)) { redirect }
but it's kind of boring and violates DRY. Imaging several entities, nested view models with entities... too cumbersome. I'd better have something like ModelState.IsValidUserData and ModelState.IsValidCriticalData. But there's no such thing.
Now, EntityId is actually bound using my custom model binder, that knows that it is mission-critical. So there's this solution:
Usual fields do populate ModelState with errors as usual.
(a) Critical fields are bound using custom model binder that throws special "CriticalModelErrorException". Controller actions have [HandleCrirticalError("action", "controller')] attribute - which handles critical errors and redirects to the given action.
(b) Critical fields are bound using custom model binder that sets BaseController.CriticalModelErrors property (obviously all controllers are derived from supercontroller base class). Each action is free to check both ModelState.IsValid and base.CriticalModelErrors and behave freely based on that.
(c) Critical fields are bound using custom model binder that sets special-format model state errors, e.g. AddModelError(name, "!CRITICAL! text"; Then base controller have method that detects such strings.
2a example:
[HandleCriticalError("Create")] // uses the same controller
[HandleModelStateError("Edit")] // redisplays page with validation errors
public ActionResult Edit(ViewModel data)
{
// here we know both our data entities and user data are valid and safe
}
2b example
public ActionResult Edit(ViewModel data)
{
if (!ModelState.IsValid)
return View(data);
if (base.CriticalModelErrors.Count > 0)
return RedirectToAction("Create");
// here we know both our data entities and user data are valid and safe
}
2c example
protected bool HasCriticalErrors()
{
return ModelState.Any(x => x.Value.Errors.Any(x => x.ErrorMessage.StartsWith("!CRITICAL!")))
}
// then same as 2b
Now, the questions: how it's handled by other apps and developers (you and your apps)? Which one would you prefer? Are there any drawbacks or better solutions?
Use the one that introduces the most amount of automation for you, so you won't have to repeat the same code on my places. As you have done already I'd go with 2a but make it a bit different so I wouldn't put those attributes on every single action but rather on the whole Controller class. If at all possible (if required by at least majority actions). If you need to exclude certain actions, create a separate action filter that will disable redirecting.
Preamble: this is a bit of a philosophical question. I'm looking more for the "right" way to do this rather than "a" way to do this.
Let's imagine I have some products, and an ASP.NET MVC application performing CRUD on those products:-
mysite.example/products/1
mysite.example/products/1/edit
I'm using the repository pattern, so it doesn't matter where these products come from:-
public interface IProductRepository
{
IEnumberable<Product> GetProducts();
....
}
Also my Repository describes a list of Users, and which products they are managers for (many-many between Users and Products). Elsewhere in the application, a Super-Admin is performing CRUD on Users and managing the relationship between Users and the Products they are permitted to manage.
Anyone is allowed to view any product, but only users who are designated as "admins" for a particular product are allowed to invoke e.g. the Edit action.
How should I go about implementing that in ASP.NET MVC? Unless I've missed something, I can't use the built-in ASP.NET Authorize attribute as first I'd need a different role for every product, and second I won't know which role to check for until I've retrieved my Product from the Repository.
Obviously you can generalise from this scenario to most content-management scenarios - e.g. Users are only allowed to edit their own Forum Posts. StackOverflow users are only allowed to edit their own questions - unless they've got 2000 or more rep...
The simplest solution, as an example, would be something like:-
public class ProductsController
{
public ActionResult Edit(int id)
{
Product p = ProductRepository.GetProductById(id);
User u = UserService.GetUser(); // Gets the currently logged in user
if (ProductAdminService.UserIsAdminForProduct(u, p))
{
return View(p);
}
else
{
return RedirectToAction("AccessDenied");
}
}
}
My issues:
Some of this code will need to be repeated - imagine there are several operations (Update, Delete, SetStock, Order, CreateOffer) depending on the User-Products relationship. You'd have to copy-paste several times.
It's not very testable - you've got to mock up by my count four objects for every test.
It doesn't really seem like the controller's "job" to be checking whether the user is allowed to perform the action. I'd much rather a more pluggable (e.g. AOP via attributes) solution. However, would that necessarily mean you'd have to SELECT the product twice (once in the AuthorizationFilter, and again in the Controller)?
Would it be better to return a 403 if the user isn't allowed to make this request? If so, how would I go about doing that?
I'll probably keep this updated as I get ideas myself, but I'm very eager to hear yours!
Thanks in advance!
Edit
Just to add a bit of detail here. The issue I'm having is that I want the business rule "Only users with permission may edit products" to be contained in one and only one place. I feel that the same code which determines whether a user can GET or POST to the Edit action should also be responsible for determining whether to render the "Edit" link on the Index or Details views. Maybe that's not possible/not feasible, but I feel like it should be...
Edit 2
Starting a bounty on this one. I've received some good and helpful answers, but nothing that I feel comfortable "accepting". Bear in mind that I'm looking for a nice clean method to keep the business logic that determines whether or not the "Edit" link on the index view will be displayed in the same place that determines whether or not a request to Products/Edit/1 is authorised or not. I'd like to keep the pollution in my action method to an absolute minimum. Ideally, I'm looking for an attribute-based solution, but I accept that may be impossible.
First of all, I think you already half-way figured it, becuase you stated that
as first I'd need a different role for every product, and second I won't know which role to check for until I've retrieved my Product from the Repository
I've seen so many attempts at making role-based security do something it was never intended to do, but you are already past that point, so that's cool :)
The alternative to role-based security is ACL-based security, and I think that is what you need here.
You will still need to retrieve the ACL for a product and then check if the user has the right permission for the product. This is so context-sensitive and interaction-heavy that I think that a purely declarative approach is both too inflexible and too implicit (i.e. you may not realize how many database reads are involved in adding a single attribute to some code).
I think scenarios like this are best modeled by a class that encapsulates the ACL logic, allowing you to either Query for decision or making an Assertion based on the current context - something like this:
var p = this.ProductRepository.GetProductById(id);
var user = this.GetUser();
var permission = new ProductEditPermission(p);
If you just want to know whether the user can edit the product, you can issue a Query:
bool canEdit = permission.IsGrantedTo(user);
If you just want to ensure that the user has rights to continue, you can issue an Assertion:
permission.Demand(user);
This should then throw an exception if the permission is not granted.
This all assumes that the Product class (the variable p) has an associated ACL, like this:
public class Product
{
public IEnumerable<ProductAccessRule> AccessRules { get; }
// other members...
}
You might want to take a look at System.Security.AccessControl.FileSystemSecurity for inspiration about modeling ACLs.
If the current user is the same as Thread.CurrentPrincipal (which is the case in ASP.NET MVC, IIRC), you can simplyfy the above permission methods to:
bool canEdit = permission.IsGranted();
or
permission.Demand();
because the user would be implicit. You can take a look at System.Security.Permissions.PrincipalPermission for inspiration.
From what you are describing it sounds like you need some form of user access control rather than role based permissions. If this is the case then it needs to be implemented throughout your business logic. Your scenario sounds like you can implement it in your service layer.
Basically you have to implement all functions in your ProductRepository from the perspective of the current user and the products are tagged with permissions for that user.
It sounds more difficult than it actually is. First off you need a user token interface that contains the user information of uid and role list (if you want to use roles). You can use IPrincipal or create your own along the lines of
public interface IUserToken {
public int Uid { get; }
public bool IsInRole(string role);
}
Then in your controller you parse the user token into your Repository constructor.
IProductRepository ProductRepository = new ProductRepository(User); //using IPrincipal
If you're using FormsAuthentication and a custom IUserToken then you can create a Wrapper around the IPrincipal so your ProductRepository is created like:
IProductRepository ProductRepository = new ProductRepository(new IUserTokenWrapper(User));
Now all your IProductRepository functions should access the user token to check permissions. For example:
public Product GetProductById(productId) {
Product product = InternalGetProductById(UserToken.uid, productId);
if (product == null) {
throw new NotAuthorizedException();
}
product.CanEdit = (
UserToken.IsInRole("admin") || //user is administrator
UserToken.Uid == product.CreatedByID || //user is creator
HasUserPermissionToEdit(UserToken.Uid, productId) //other custom permissions
);
}
If you wondering about getting a list of all products, in your data access code you can query based on permission. In your case a left join to see if the many-to-many table contains the UserToken.Uid and the productId. If the right side of the join is present you know the user has permission to that product and then you can set your Product.CanEdit boolean.
Using this method you can then use the following, if you like, in your View (where Model is your Product).
<% if(Model.CanEdit) { %>
Edit
<% } %>
or in your controller
public ActionResult Get(int id) {
Product p = ProductRepository.GetProductById(id);
if (p.CanEdit) {
return View("EditProduct");
}
else {
return View("Product");
}
}
The benefit to this method is that the security is built in to your service layer (ProductRepository) so it is not handled by your controllers and cannot be bypassed by your controllers.
The main point is that the security is placed in your business logic and not in your controller.
The copy paste solutions really become tedious after a while, and is really annoying to maintain. I would probably go with a custom attribute doing what you need. You can use the excellent .NET Reflector to see how the AuthorizeAttribute is implemented and perform your own logic to it.
What it does is inheriting FilterAttribute and implementing IAuthorizationFilter. I can't test this at the moment, but something like this should work.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ProductAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
object productId;
if (!filterContext.RouteData.Values.TryGetValue("productId", out productId))
{
filterContext.Result = new HttpUnauthorizedResult();
return;
}
// Fetch product and check for accessrights
if (user.IsAuthorizedFor(productId))
{
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
cache.SetProxyMaxAge(new TimeSpan(0L));
cache.AddValidationCallback(new HttpCacheValidateHandler(this.Validate), null);
}
else
filterContext.Result = new HttpUnauthorizedResult();
}
private void Validate(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
// The original attribute performs some validation in here as well, not sure it is needed though
validationStatus = HttpValidationStatus.Valid;
}
}
You could probably also store the product/user that you fetch in the filterContext.Controller.TempData so you can fetch it in the controller, or store it in some cache.
Edit: I just noticed the part about the edit link. The best way I can think of is factoring out the authorization part from the attribute and make a HttpHelper for it that you can use in your view.
I tend to think that authorization is part of your business logic (or at least outside of your controller logic anyway). I agree with kevingessner above, in that the authorization check should be part of the call to fetch the item. In his OnException method, you could show the login page (or whatever you have configured in the web.config) by something like this:
if (...)
{
Response.StatusCode = 401;
Response.StatusDescription = "Unauthorized";
HttpContext.Response.End();
}
And instead of making UserRepository.GetUserSomehowFromTheRequest() calls in all the action methods, I would do this once (in an override of the Controller.OnAuthorization method for example), then stick that data somewhere in your controller base class for later use (e.g. a property).
I think that it's unrealistic, and a violation of the separation of concerns, to expect to have controller/model code control what the view renders. The controller/model code can set a flag, in the view model, that the view can use to determine what it should do, but I don't think that you should expect a single method to be used by both controller/model and view to control both access to and rendering of the model.
Having said that you could approach this in either of two ways -- both would involve a view model that carries some annotations used by the view in addition to the actual model. In the first case, you can use an attribute to control access to the action. This would be my preference, but would involve decorating each method independently -- unless all of the actions in a controller have the same access attributes.
I've developed a "role or owner" attribute for just this purpose. It verifies that the user is in a particular role or is the owner of the data being produced by the method. Ownership, in my case, is controlled by the presence of a foreign key relationship between the user and the data in question -- that is, you have a ProductOwner table and there needs to be a row containing the product/owner pair for the product and current user. It differs from the normal AuthorizeAttribute in that when the ownership or role check fails, the user is directed to an error page, not the login page. In this case, each method would need to set a flag in the view model that indicates that the model can be edited.
Alternatively, you could implement similar code in the ActionExecuting/ActionExecuted methods of the controller (or a base controller so that it applies consistently across all controllers). In this case, you would need to write some code to detect what kind of action is being executed so you know whether to abort the action based on the ownership of the product in question. The same method would set the flag to indicate that the model can be edited. In this case, you'd probably need a model hierarchy so you could cast the model as an editable model so that you can set the property regardless of the specific model type.
This option seems more coupled to me than using the attribute and arguably more complicated. In the case of the attribute you can design it so that it takes the various table and property names as attributes to the attribute and uses reflection to get the proper data from your repository based on the attribute's properties.
Answering my own question (eep!), Chapter 1 of Professional ASP.NET MVC 1.0 (the NerdDinner tutorial) recommends a similar solution to mine above:
public ActionResult Edit(int id)
{
Dinner dinner = dinnerRepositor.GetDinner(id);
if(!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
return View(new DinnerFormViewModel(dinner));
}
Asides from making me hungry for my dinner, this doesn't really add anything as the tutorial goes on to repeat the code implementing the business rule immediately in the matching POST Action Method, and in the Details view (actually in a child partial of the Details view)
Does that violate SRP? If the business rule changed (so that e.g. anyone who had RSVP'd could edit the dinner), you'd have to change both GET and POST methods, and the View (and the GET and POST methods and View for the Delete operation too, although that's technically a seperate business rule).
Is pulling the logic out into some kind of permissions arbitrator object (as I've done above) as good as it gets?
You're on the right track, but you can encapsulate all of the permission check into a single method like GetProductForUser, which takes a product, user, and the required permission. By throwing an exception that's caught in the controller's OnException handler, the handling is all in one place:
enum Permission
{
Forbidden = 0,
Access = 1,
Admin = 2
}
public class ProductForbiddenException : Exception
{ }
public class ProductsController
{
public Product GetProductForUser(int id, User u, Permission perm)
{
Product p = ProductRepository.GetProductById(id);
if (ProductPermissionService.UserPermission(u, p) < perm)
{
throw new ProductForbiddenException();
}
return p;
}
public ActionResult Edit(int id)
{
User u = UserRepository.GetUserSomehowFromTheRequest();
Product p = GetProductForUser(id, u, Permission.Admin);
return View(p);
}
public ActionResult View(int id)
{
User u = UserRepository.GetUserSomehowFromTheRequest();
Product p = GetProductForUser(id, u, Permission.Access);
return View(p);
}
public override void OnException(ExceptionContext filterContext)
{
if (typeof(filterContext.Exception) == typeof(ProductForbiddenException))
{
// handle me!
}
base.OnException(filterContext);
}
}
You just have to provide ProductPermissionService.UserPermission, to return a user's permission on a given product.By using a Permission enum (I think I've got the right syntax...) and comparing permissions with <, Admin permissions imply Access permissions, which is pretty much always right.
You can use a XACML based implementation. This way you can externalize authorization and also have a repository for your policies outside of your code.
When I use the default model binding to bind form parameters to a complex object which is a parameter to an action, the framework remembers the values passed to the first request, meaning that any subsequent request to that action gets the same data as the first. The parameter values and validation state are persisted between unrelated web requests.
Here is my controller code (service represents access to the back end of the app):
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create()
{
return View(RunTime.Default);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(RunTime newRunTime)
{
if (ModelState.IsValid)
{
service.CreateNewRun(newRunTime);
TempData["Message"] = "New run created";
return RedirectToAction("index");
}
return View(newRunTime);
}
My .aspx view (strongly typed as ViewPage<RunTime>) contains directives like:
<%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %>
This uses the DefaultModelBinder class, which is meant to autobind my model's properties.
I hit the page, enter valid data (e.g. time = 1). The app correctly saves the new object with time = 1. I then hit it again, enter different valid data (e.g. time = 2). However the data that gets saved is the original (e.g. time = 1). This also affects validation, so if my original data was invalid, then all data I enter in the future is considered invalid. Restarting IIS or rebuilding my code flushes the persisted state.
I can fix the problem by writing my own hard-coded model binder, a basic naive example of which is shown below.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([ModelBinder(typeof (RunTimeBinder))] RunTime newRunTime)
{
if (ModelState.IsValid)
{
service.CreateNewRun(newRunTime);
TempData["Message"] = "New run created";
return RedirectToAction("index");
}
return View(newRunTime);
}
internal class RunTimeBinder : DefaultModelBinder
{
public override ModelBinderResult BindModel(ModelBindingContext bindingContext)
{
// Without this line, failed validation state persists between requests
bindingContext.ModelState.Clear();
double time = 0;
try
{
time = Convert.ToDouble(bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"]);
}
catch (FormatException)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".Time", bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"] + "is not a valid number");
}
var model = new RunTime(time);
return new ModelBinderResult(model);
}
}
Am I missing something? I don't think it's a browser session problem as I can reproduce the problem if the first data is entered in one browser and the second in another.
It turns out that the problem was that my controllers were being reused between calls. One of the details I chose to omit from my original post is that I am using the Castle.Windsor container to create my controllers. I had failed to mark my controller with the Transient lifestyle, so I was getting the same instance back on each request. Thus the context being used by the binder was being re-used and of course it contained stale data.
I discovered the problem while carefully analysing the difference between Eilon's code and mine, eliminating all other possibilities. As the Castle documentation says, this is a "terrible mistake"! Let this be a warning to others!
Thanks for your response Eilon - sorry to take up your time.
I tried to reproduce this problem but I'm not seeing that same behavior. I created almost exactly the same controller and views that you have (with some assumptions) and every time I created a new "RunTime" I put its value in TempData and sent it off through the Redirect. Then on the target page I grabbed the value and it was always the value I typed in on that request - never a stale value.
Here's my Controller:
public class HomeController : Controller {
public ActionResult Index() {
ViewData["Title"] = "Home Page";
string message = "Welcome: " + TempData["Message"];
if (TempData.ContainsKey("value")) {
int theValue = (int)TempData["value"];
message += " " + theValue.ToString();
}
ViewData["Message"] = message;
return View();
}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create() {
return View(RunTime.Default);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(RunTime newRunTime) {
if (ModelState.IsValid) {
//service.CreateNewRun(newRunTime);
TempData["Message"] = "New run created";
TempData["value"] = newRunTime.TheValue;
return RedirectToAction("index");
}
return View(newRunTime);
}
}
And here's my View (Create.aspx):
<% using (Html.BeginForm()) { %>
<%= Html.TextBox("newRunTime.TheValue", ViewData.Model.TheValue) %>
<input type="submit" value="Save" />
<% } %>
Also, I wasn't sure what the "RunTime" type looked like, so I made this one:
public class RunTime {
public static readonly RunTime Default = new RunTime(-1);
public RunTime() {
}
public RunTime(int theValue) {
TheValue = theValue;
}
public int TheValue {
get;
set;
}
}
Is it possible that your implementation of RunTime includes some static values or something?
Thanks,
Eilon
I'm not sure if this is related or not, but your call to
<%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %>
might actually pick the wrong overload (since Time is an integer, it will pick the object htmlAttributes overload, rather than string value.
Checking the rendered HTML will let you know if this is occurring. changing int to ViewData.Model.Time.ToString() will force the correct overload.
It sounds like your issue is something different, but I noticed that and have been burned in the past.
Seb, I'm not sure what you mean by an example. I don't know anything about Unity configuration. I'll explain the situation with Castle.Windsor and maybe that will help you with to configure Unity correctly.
By default, Castle.Windsor returns the same object each time you request a given type. This is the singleton lifestyle. There's a good explanation of the various lifestyle options in the Castle.Windsor documentation.
In ASP.NET MVC, each instance of a controller class is bound to the context of the web request that it was created to serve. So if your IoC container returns the same instance of your controller class each time, you'll always get a controller bound to the context of the first web request that used that controller class. In particular, the ModelState and other objects used by the DefaultModelBinder will be reused, so your bound model object and the validation messages in the ModelState will be stale.
Therefore you need your IoC to return a new instance each time MVC requests an instance of your controller class.
In Castle.Windsor, this is called the transient lifestyle. To configure it, you have two options:
XML configuration: you add lifestlye="transient" to each element in your configuration file that represents a controller.
In-code configuration: you can tell the container to use the transient lifestyle at the time you register the controller. This is what the MvcContrib helper that Ben mentioned does automatically for you - take a look at the method RegisterControllers in the MvcContrib source code.
I would imagine that Unity offers a similiar concept to the lifestyle in Castle.Windsor, so you'll need to configure Unity to use its equivalent of the transient lifestyle for your controllers.
MvcContrib appears to have some Unity support - maybe you could look there.
Hope this helps.
Having come across similar problems when attempting to use the Windsor IoC container in an ASP.NET MVC app I had to go through the same voyage of discovery to get it working. Here are some of the details that might help someone else.
Using this is the initial setup in the Global.asax:
if (_container == null)
{
_container = new WindsorContainer("config/castle.config");
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container));
}
And using a WindsorControllerFactory which when asked for a controller instance does:
return (IController)_container.Resolve(controllerType);
Whilst Windsor was correctly linking up all of the controllers, for some reason parameters were not being passed from the form to the relevant controller action. Instead they were all null, though it was calling the correct action.
The default is for the container to pass back singletons, obviously a bad thing for controllers and the cause of the problem:
http://www.castleproject.org/monorail/documentation/trunk/integration/windsor.html
However the documentation does point out that the lifestyle of the controllers can be changed to transient, though it doesn't actually tell you how to do that if you are using a configuration file. Turns out it's easy enough:
<component
id="home.controller"
type="DoYourStuff.Controllers.HomeController, DoYourStuff"
lifestyle="transient" />
And without any code changes it should now work as expected (i.e. unique controllers every time provided by the one instance of the container). You can then do all of your IoC configuration in the config file rather than the code like the good boy/girl that I know you are.