MVC Filter ViewResult - asp.net-mvc

I have some filters set up on an MVC C# framework. From here I am try to render an error page. The error page renders correctly, but I want to pass data from the HandleUnautorizedRequest (that depends on which filter you fail) so far I have this. Is there any way to do something like this, but pass data over to the error page I have in shared. I have already unsuccessfully tried to use ViewData in the object constructor, but I might have just been doing it wrong.
The way our code base is structured I can't initialize any of my controllers from here either.
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new ViewResult
{
ViewName = "Error"
};
}

Not sure how you tried to use ViewData, but you could give this a shot:
filterContext.Result = new ViewResult()
{
ViewName = "test",
ViewData = new ViewDataDictionary()
{
{ "key", "value"}
}
};

Related

Initializing ViewData property inside ViewResult object

When I access ViewData inside a method in the controller, I am able to assign the value in the form of dictionary ie.
ViewData["message"]="this is a custom message";
but I got into a scenario where I was trying to handle the Exception in MVC, here is my code:
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled && (filterContext.Exception is ArgumentOutOfRangeException))
{
filterContext.Result = new ViewResult { ViewName = "Error", ViewData = };
filterContext.ExceptionHandled = true;
}
}
Now when handling exception i would like to pass a message to the error page, so tried to access the Result property of the ExceptionContext.
Now
my question is why am I not able to assign a value to the ViewData in
a dictionary-like a format here
filterContext.Result = new ViewResult { ViewName = "Error", ViewData = };
This is also a property returning a ViewDataDictionary object, when am I able to assign a value in the Controller method like this ViewData["message"] = "argument exception error"; the why am I not able to do the same inside the ViewResult object.
I tried it myself and got an understanding on the inner workings of the MVC frameWork, please correct me if I am wrong and please provide an explanation for it, which would make to learn more about the framework.
When I access ViewData inside a method in the controller, I am able to
assign the value in the form of dictionary
This is because when we call the controller and the method, MVC takes responsibilty to assign objects to all the properties, thats the reason we could assign value for the ViewData inside the method.
filterContext.Result = new ViewResult { ViewName = "Error", ViewData =
};
When we are dealing with the ViewData property of the ViewResult class, we cannot assign the value, in this way ViewData["key"]="some value" because it requires ViewDataDictionary object. however we can do this to assign the value like this
var d = new ViewDataDictionary();
d["key"] = "some value";
filterContext.Result = new ViewResult { ViewName = "Error",ViewData=d };

ActionInvoker.InvokeAction(context, "Method") - how to pass arguments to invoked method?

Hi I have my mvc app and this code snippet:
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
ActionInvoker.InvokeAction(filterContext, "ErrorMessage");
}
public ActionResult ErrorMessage(ExceptionContext filterContext)
{
ViewModel<Exception> viewModel = ViewModelFactory.CreateFor(filterContext.Exception);
return View(viewModel);
}
The problem is that I can't pass arguments to method. I thought it would be this filterContext but in ErrorMessage method it has all default values.
So my question is - How to pass some values to method that I invoke?
I don't know if it was clear from my question but what I wanted achive was that I didn't wanted to catch any exception in my controllers actions and still get good-looking message about error. So my solution which satisfies me is:
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
TempData.Add(MyCommons.Exception, filterContext.Exception);
var controllerName = filterContext.RequestContext.RouteData.Values["Controller"];
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary(new {controller = controllerName, action = "Error"}));
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
}
You dont pass parameters to the invoked action, use model binding instead.
If you want to pass some custom parameters out of default model binding sources (query, form, route..), wrap them in some class and implement your custom model binder for it (or valueprovider, but i think modelbinder is more appropriate here).

Changing the view in an ASP.NET MVC Filter

I want to redirect the user to a different view if they are using a mobile browser. I've decided I'd like to do this using MVC filters by applying it to actions which I want to have a mobile view.
I believe this redirect needs to happen in OnActionExecuted, however the filterContext does not contain information on the view - it does, however in OnResultExecuted, but by this time I believe it is too late to change the view.
How can I intercept the view name and change the ViewResult?
This is what I have in the result executed and what I'd like to have work in Action Executed.
public class MobilePageFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Result is ViewResult)
{
if (isMobileSite(filterContext.HttpContext.Session[SetMobile.SESSION_USE_MOBILE]))
{
ViewResult viewResult = (ViewResult)filterContext.Result;
string viewName = viewResult.ViewName;
filterContext.Result = new ViewResult
{
ViewName = "Mobile/" + viewName,
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
}
}
base.OnResultExecuted(filterContext);
}
}
I would recommend you the following blog post which explains a better alternative to achieve what you are asking for rather than using action filters.
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!

Unit tests on MVC validation

How can I test that my controller action is putting the correct errors in the ModelState when validating an entity, when I'm using DataAnnotation validation in MVC 2 Preview 1?
Some code to illustrate. First, the action:
[HttpPost]
public ActionResult Index(BlogPost b)
{
if(ModelState.IsValid)
{
_blogService.Insert(b);
return(View("Success", b));
}
return View(b);
}
And here's a failing unit test that I think should be passing but isn't (using MbUnit & Moq):
[Test]
public void When_processing_invalid_post_HomeControllerModelState_should_have_at_least_one_error()
{
// arrange
var mockRepository = new Mock<IBlogPostSVC>();
var homeController = new HomeController(mockRepository.Object);
// act
var p = new BlogPost { Title = "test" }; // date and content should be required
homeController.Index(p);
// assert
Assert.IsTrue(!homeController.ModelState.IsValid);
}
I guess in addition to this question, should I be testing validation, and should I be testing it in this way?
Hate to necro a old post, but I thought I'd add my own thoughts (since I just had this problem and ran across this post while seeking the answer).
Don't test validation in your controller tests. Either you trust MVC's validation or write your own (i.e. don't test other's code, test your code)
If you do want to test validation is doing what you expect, test it in your model tests (I do this for a couple of my more complex regex validations).
What you really want to test here is that your controller does what you expect it to do when validation fails. That's your code, and your expectations. Testing it is easy once you realize that's all you want to test:
[test]
public void TestInvalidPostBehavior()
{
// arrange
var mockRepository = new Mock<IBlogPostSVC>();
var homeController = new HomeController(mockRepository.Object);
var p = new BlogPost();
homeController.ViewData.ModelState.AddModelError("Key", "ErrorMessage"); // Values of these two strings don't matter.
// What I'm doing is setting up the situation: my controller is receiving an invalid model.
// act
var result = (ViewResult) homeController.Index(p);
// assert
result.ForView("Index")
Assert.That(result.ViewData.Model, Is.EqualTo(p));
}
I had been having the same problem, and after reading Pauls answer and comment, I looked for a way of manually validating the view model.
I found this tutorial which explains how to manually validate a ViewModel that uses DataAnnotations. They Key code snippet is towards the end of the post.
I amended the code slightly - in the tutorial the 4th parameter of the TryValidateObject is omitted (validateAllProperties). In order to get all the annotations to Validate, this should be set to true.
Additionaly I refactored the code into a generic method, to make testing of ViewModel validation simple:
public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate)
where TController : ApiController
{
var validationContext = new ValidationContext(viewModelToValidate, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
foreach (var validationResult in validationResults)
{
controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
}
}
So far this has worked really well for us.
When you call the homeController.Index method in your test, you aren't using any of the MVC framework that fires off the validation so ModelState.IsValid will always be true. In our code we call a helper Validate method directly in the controller rather than using ambient validation. I haven't had much experience with the DataAnnotations (We use NHibernate.Validators) maybe someone else can offer guidance how to call Validate from within your controller.
I'm using ModelBinders in my test cases to be able to update model.IsValid value.
var form = new FormCollection();
form.Add("Name", "0123456789012345678901234567890123456789");
var model = MvcModelBinder.BindModel<AddItemModel>(controller, form);
ViewResult result = (ViewResult)controller.Add(model);
With my MvcModelBinder.BindModel method as follows (basically the same code used
internally in the MVC framework):
public static TModel BindModel<TModel>(Controller controller, IValueProvider valueProvider) where TModel : class
{
IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(TModel));
ModelBindingContext bindingContext = new ModelBindingContext()
{
FallbackToEmptyPrefix = true,
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel)),
ModelName = "NotUsedButNotNull",
ModelState = controller.ModelState,
PropertyFilter = (name => { return true; }),
ValueProvider = valueProvider
};
return (TModel)binder.BindModel(controller.ControllerContext, bindingContext);
}
I was researching this today and I found this blog post by Roberto Hernández (MVP) that seems to provide the best solution to fire the validators for a controller action during unit testing. This will put the correct errors in the ModelState when validating an entity.
This doesn't exactly answer your question, because it abandons DataAnnotations, but I'll add it because it might help other people write tests for their Controllers:
You have the option of not using the validation provided by System.ComponentModel.DataAnnotations but still using the ViewData.ModelState object, by using its AddModelError method and some other validation mechanism. E.g:
public ActionResult Create(CompetitionEntry competitionEntry)
{
if (competitionEntry.Email == null)
ViewData.ModelState.AddModelError("CompetitionEntry.Email", "Please enter your e-mail");
if (ModelState.IsValid)
{
// insert code to save data here...
// ...
return Redirect("/");
}
else
{
// return with errors
var viewModel = new CompetitionEntryViewModel();
// insert code to populate viewmodel here ...
// ...
return View(viewModel);
}
}
This still lets you take advantage of the Html.ValidationMessageFor() stuff that MVC generates, without using the DataAnnotations. You have to make sure the key you use with AddModelError matches what the view is expecting for validation messages.
The controller then becomes testable because the validation is happening explicitly, rather than being done automagically by the MVC framework.
I agree that ARM has the best answer: test the behaviour of your controller, not the built-in validation.
However, you can also unit test that your Model/ViewModel has the correct validation attributes defined. Let's say your ViewModel looks like this:
public class PersonViewModel
{
[Required]
public string FirstName { get; set; }
}
This unit test will test for the existence of the [Required] attribute:
[TestMethod]
public void FirstName_should_be_required()
{
var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName");
var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false)
.FirstOrDefault();
Assert.IsNotNull(attribute);
}
In contrast to ARM, I don't have a problem with grave digging. So here is my suggestion. It builds on the answer of Giles Smith and works for ASP.NET MVC4 (I know the question is about MVC 2, but Google doesn't discriminate when looking for answers and I cannot test on MVC2.)
Instead of putting the validation code in a generic static method, I put it in a test controller. The controller has everything needed for validation. So, the test controller looks like this:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Wbe.Mvc;
protected class TestController : Controller
{
public void TestValidateModel(object Model)
{
ValidationContext validationContext = new ValidationContext(Model, null, null);
List<ValidationResult> validationResults = new List<ValidationResult>();
Validator.TryValidateObject(Model, validationContext, validationResults, true);
foreach (ValidationResult validationResult in validationResults)
{
this.ModelState.AddModelError(String.Join(", ", validationResult.MemberNames), validationResult.ErrorMessage);
}
}
}
Of course the class does not need to be a protected innerclass, that is the way I use it now but I probably am going to reuse that class. If somewhere there is a model MyModel that is decorated with nice data annotation attributes, then the test looks something like this:
[TestMethod()]
public void ValidationTest()
{
MyModel item = new MyModel();
item.Description = "This is a unit test";
item.LocationId = 1;
TestController testController = new TestController();
testController.TestValidateModel(item);
Assert.IsTrue(testController.ModelState.IsValid, "A valid model is recognized.");
}
The advantage of this setup is that I can reuse the test controller for tests of all my models and may be able to extend it to mock a bit more about the controller or use the protected methods that a controller has.
Hope it helps.
If you care about validation but you don't care about how it is implemented, if you only care about validation of your action method at the highest level of abstraction, no matter whether it is implemented as using DataAnnotations, ModelBinders or even ActionFilterAttributes, then you could use Xania.AspNet.Simulator nuget package as follows:
install-package Xania.AspNet.Simulator
--
var action = new BlogController()
.Action(c => c.Index(new BlogPost()), "POST");
var modelState = action.ValidateRequest();
modelState.IsValid.Should().BeFalse();
Based on #giles-smith 's answer and comments, for Web API:
public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate)
where TController : ApiController
{
var validationContext = new ValidationContext(viewModelToValidate, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
foreach (var validationResult in validationResults)
{
controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
}
}
See on answer edit above...
#giles-smith's answer is my preferred approach but the implementation can be simplified:
public static void ValidateViewModel(this Controller controller, object viewModelToValidate)
{
var validationContext = new ValidationContext(viewModelToValidate, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
foreach (var validationResult in validationResults)
{
controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
}
}
Instead of passing in a BlogPost you can also declare the actions parameter as FormCollection. Then you can create the BlogPost yourself and call UpdateModel(model, formCollection.ToValueProvider());.
This will trigger the validation for any field in the FormCollection.
[HttpPost]
public ActionResult Index(FormCollection form)
{
var b = new BlogPost();
TryUpdateModel(model, form.ToValueProvider());
if (ModelState.IsValid)
{
_blogService.Insert(b);
return (View("Success", b));
}
return View(b);
}
Just make sure your test adds a null value for every field in the views form that you want to leave empty.
I found that doing it this way, at the expense of a few extra lines of code, makes my unit tests resemble the way the code gets called at runtime more closely making them more valuable. Also you can test what happens when someone enters "abc" in a control bound to an int property.

Return View from ActionFilter

I have an ActionFilter that checks if a parameter in the URL is valid.
If it is not valid I have to render a View. I dont want to redirect, because I still need the ActionExecutingContext.
Can that be done?
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Guid processIdentifier = (Guid)filterContext.RouteData.Values["processIdentifier"];
//if processIdentifier not found render a view with message and some other objects in ViewData
filterContext.Controller.ViewData.ModelState.AddModelError("WrongProcessIdentifier", "The process-id you supplied is not valid");
base.OnActionExecuting(filterContext);
}
HandleErrorAttribute had what I was looking for.
filterContext.Result = new ViewResult
{
ViewName = "MessagePage",
ViewData = filterContext.Controller.ViewData,
TempData = filterContext.Controller.TempData
};
Yes. Look at the source for HandleErrorAttribute.
Try this
[HandleError]
public ActionResult MyAction (int id)
{
// ...
}
And put the view you want rendered in to ~/Views/Shared/Error.ascx.

Resources