send object through ViewData.Model in asp.net mvc - asp.net-mvc

I need your help.
I tried to pass object form the view to the controller using ViewData.Model
this is the index method in the controller
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
dynamic stronglytyped = new { Amount = 10, Size = 20 };
List<dynamic> ListOfAnynomous = new List<object> { new { amount = 10 } };
ViewData.Model = ListOfAnynomous[0];
return View();
}
and this is the view part
<div>
#Model.amount
</div>
this is the erro
'object' does not contain a definition for 'amount'
please could anyone help me.

The exception is thrown because you passing an anonymous object. Anonymous types are internal, so their properties can't be seen outside their defining assembly. This article gives a good explanation.
While you can use the html helpers to render the properties, for example
#Html.DisplayFor("amount")
you will also lose intellisense and you app will be difficult to debug.
Instead use a view model to represent what you want to display/edit and pass the model to the view.

Your code is wrong.
If you want to use the model object you have pass it to the view:
return View(ListOfAnynomous[0]);
After you will be able to use the "Model" property.
ViewData is another container not related to the model property.
In the end your method will look like this:
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
dynamic stronglytyped = new { Amount = 10, Size = 20 };
List<dynamic> ListOfAnynomous = new List<object> { new { amount = 10 } };
// ViewData.Model = ListOfAnynomous[0];
return View(ListOfAnynomous[0]);
}

Related

Difference between View Data,View Bag and Temp Data in mvc [duplicate]

Could any body explain, when to use
TempData
ViewBag
ViewData
I have a requirement, where I need to set a value in a controller one, that controller will redirect to Controller Two and Controller Two will render the View.
I have tried to use ViewBag, the value gets lost by the time I reach Controller Two.
Can I know when to use and advantages or disadvantages?
Thanks
1)TempData
Allows you to store data that will survive for a redirect. Internally it uses the Session as backing store, after the redirect is made the data is automatically evicted. The pattern is the following:
public ActionResult Foo()
{
// store something into the tempdata that will be available during a single redirect
TempData["foo"] = "bar";
// you should always redirect if you store something into TempData to
// a controller action that will consume this data
return RedirectToAction("bar");
}
public ActionResult Bar()
{
var foo = TempData["foo"];
...
}
2)ViewBag, ViewData
Allows you to store data in a controller action that will be used in the corresponding view. This assumes that the action returns a view and doesn't redirect. Lives only during the current request.
The pattern is the following:
public ActionResult Foo()
{
ViewBag.Foo = "bar";
return View();
}
and in the view:
#ViewBag.Foo
or with ViewData:
public ActionResult Foo()
{
ViewData["Foo"] = "bar";
return View();
}
and in the view:
#ViewData["Foo"]
ViewBag is just a dynamic wrapper around ViewData and exists only in ASP.NET MVC 3.
This being said, none of those two constructs should ever be used. You should use view models and strongly typed views. So the correct pattern is the following:
View model:
public class MyViewModel
{
public string Foo { get; set; }
}
Action:
public Action Foo()
{
var model = new MyViewModel { Foo = "bar" };
return View(model);
}
Strongly typed view:
#model MyViewModel
#Model.Foo
After this brief introduction let's answer your question:
My requirement is I want to set a value in a controller one, that
controller will redirect to ControllerTwo and Controller2 will render
the View.
public class OneController: Controller
{
public ActionResult Index()
{
TempData["foo"] = "bar";
return RedirectToAction("index", "two");
}
}
public class TwoController: Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
Foo = TempData["foo"] as string
};
return View(model);
}
}
and the corresponding view (~/Views/Two/Index.cshtml):
#model MyViewModel
#Html.DisplayFor(x => x.Foo)
There are drawbacks of using TempData as well: if the user hits F5 on the target page the data will be lost.
Personally I don't use TempData neither. It's because internally it uses Session and I disable session in my applications. I prefer a more RESTful way to achieve this. Which is: in the first controller action that performs the redirect store the object in your data store and user the generated unique id when redirecting. Then on the target action use this id to fetch back the initially stored object:
public class OneController: Controller
{
public ActionResult Index()
{
var id = Repository.SaveData("foo");
return RedirectToAction("index", "two", new { id = id });
}
}
public class TwoController: Controller
{
public ActionResult Index(string id)
{
var model = new MyViewModel
{
Foo = Repository.GetData(id)
};
return View(model);
}
}
The view stays the same.
TempData
Basically it's like a DataReader, once read, data will be lost.
Check this Video
Example
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
TempData["T"] = "T";
return RedirectToAction("About");
}
public ActionResult About()
{
return RedirectToAction("Test1");
}
public ActionResult Test1()
{
String str = TempData["T"]; //Output - T
return View();
}
}
If you pay attention to the above code, RedirectToAction has no impact over the TempData until TempData is read. So, once TempData is read, values will be lost.
How can i keep the TempData after reading?
Check the output in Action Method Test 1 and Test 2
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
TempData["T"] = "T";
return RedirectToAction("About");
}
public ActionResult About()
{
return RedirectToAction("Test1");
}
public ActionResult Test1()
{
string Str = Convert.ToString(TempData["T"]);
TempData.Keep(); // Keep TempData
return RedirectToAction("Test2");
}
public ActionResult Test2()
{
string Str = Convert.ToString(TempData["T"]); //OutPut - T
return View();
}
}
If you pay attention to the above code, data is not lost after RedirectToAction as well as after Reading the Data and the reason is, We are using TempData.Keep(). is that
In this way you can make it persist as long as you wish in other controllers also.
ViewBag/ViewData
The Data will persist to the corresponding View
ViewBag, ViewData, TempData and View State in MVC
http://royalarun.blogspot.in/2013/08/viewbag-viewdata-tempdata-and-view.html
ASP.NET MVC offers us three options ViewData, VieBag and TempData for passing data from controller to view and in next request. ViewData and ViewBag are almost similar and TempData performs additional responsibility.
Similarities between ViewBag & ViewData :
Helps to maintain data when you move from controller to view. Used to
pass data from controller to corresponding view. Short life means
value becomes null when redirection occurs. This is because their goal
is to provide a way to communicate between controllers and views. It’s
a communication mechanism within the server call.
Difference between ViewBag & ViewData:
ViewData is a dictionary of objects that is derived from
ViewDataDictionary class and accessible using strings as keys. ViewBag
is a dynamic property that takes advantage of the new dynamic features
in C# 4.0. ViewData requires typecasting for complex data type and
check for null values to avoid error. ViewBag doesn’t require
typecasting for complex data type.
ViewBag & ViewData Example:
public ActionResult Index()
{
ViewBag.Name = "Arun Prakash";
return View();
}
public ActionResult Index()
{
ViewData["Name"] = "Arun Prakash";
return View();
}
In View, we call like below:
#ViewBag.Name
#ViewData["Name"]
TempData:
Helps to maintain data when you move from one controller to other
controller or from one action to other action. In other words when you
redirect, “Tempdata” helps to maintain data between those redirects.
It internally uses session variables. TempData is meant to be a very
short-lived instance, and you should only use it during the current
and the subsequent requests only
The only scenario where using TempData will reliably work is when you are redirecting. This is because a redirect kills the current request (and sends HTTP status code 302 Object Moved to the client), then creates a new request on the server to serve the redirected view.
It requires typecasting for complex data type and check for null values to avoid error.
public ActionResult Index()
{
var model = new Review()
{
Body = "Start",
Rating=5
};
TempData["ModelName"] = model;
return RedirectToAction("About");
}
public ActionResult About()
{
var model= TempData["ModelName"];
return View(model);
}
void Keep()
Calling this method with in the current action ensures that all the items in TempData are not removed at the end of the current request.
#model MyProject.Models.EmpModel;
#{
Layout = "~/Views/Shared/_Layout.cshtml";
ViewBag.Title = "About";
var tempDataEmployeet = TempData["emp"] as Employee; //need typcasting
TempData.Keep(); // retains all strings values
}
void Keep(string key)
Calling this method with in the current action ensures that specific item in TempData is not removed at the end of the current request.
#model MyProject.Models.EmpModel;
#{
Layout = "~/Views/Shared/_Layout.cshtml";
ViewBag.Title = "About";
var tempDataEmployeet = TempData["emp"] as Employee; //need typcasting
TempData.Keep("emp"); // retains only "emp" string values
}
TempData
will be always available until first read, once you read it its not available any more can be useful to pass quick message also to view that will be gone after first read.
ViewBag
Its more useful when passing quickly piece of data to the view, normally you should pass all data to the view through model , but there is cases when you model coming direct from class that is map into database like entity framework
in that case you don't what to change you model to pass a new piece of data, you can stick that into the viewbag
ViewData is just indexed version of ViewBag and was used before MVC3
Also the scope is different between viewbag and temptdata. viewbag is based on first view (not shared between action methods) but temptdata can be shared between an action method and just one another.

MVC Filter ViewResult

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"}
}
};

How to properly populate drop downs from ViewData in controller on multiple views in ASP.NET MVC

I've said this about a million times already, but I'm still new to ASP.Net MVC (and ASP.NET in general) so sorry if this is a bit obvious...
Ok, I have a controller which sends data to two views. One view just lists data from a database, while the other allows you to edit and add records respectively (they use the same view, with different arguments passed in).
The edit view is where my question lies. I have four drop downs in the form that are populated with data from my repository (coming from 3 different tables in my database). This all works 100% absolutely fine with the code below, no errors.
My question is what is the best way to populate these drop downs? Currently, I have my view code like so, passing data through ViewData in the controller:
Edit View:
public ViewResult EditJob(int jobId)
{
IList<JobCust> customerList = jobsRepository.JobCustomers.OrderBy(Customer => Customer.CompanyName).ToList();
ViewData["customers"] = new SelectList(customerList, "CompanyName", "CompanyName");
IList<JobVehicle> vehicleRegList = jobsRepository.JobVehicles.OrderBy(Vehicle => Vehicle.VehicleReg).ToList();
ViewData["vehicleReg"] = new SelectList(vehicleRegList, "VehicleReg", "VehicleReg");
IList<JobVehicle> vehicleTypeList = jobsRepository.JobVehicles.OrderBy(Vehicle => Vehicle.VehicleType).ToList();
ViewData["vehicleType"] = new SelectList(vehicleTypeList, "VehicleType", "VehicleType");
IList<JobDriver> driverList = jobsRepository.JobDrivers.OrderBy(Driver => Driver.LastName).ToList();
ViewData["LastName"] = new SelectList(driverList, "LastName", "LastName");
var job = jobsRepository.Jobs.First(x => x.JobID == jobId);
return View(job);
}
Add View:
public ViewResult AddJob()
{
IList<JobCust> customerList = jobsRepository.JobCustomers.OrderBy(Customer => Customer.CompanyName).ToList();
ViewData["customers"] = new SelectList(customerList, "CompanyName", "CompanyName");
IList<JobVehicle> vehicleRegList = jobsRepository.JobVehicles.OrderBy(Vehicle => Vehicle.VehicleReg).ToList();
ViewData["vehicleReg"] = new SelectList(vehicleRegList, "VehicleReg", "VehicleReg");
IList<JobVehicle> vehicleTypeList = jobsRepository.JobVehicles.OrderBy(Vehicle => Vehicle.VehicleType).ToList();
ViewData["vehicleType"] = new SelectList(vehicleTypeList, "VehicleType", "VehicleType");
IList<JobDriver> driverList = jobsRepository.JobDrivers.OrderBy(Driver => Driver.LastName).ToList();
ViewData["LastName"] = new SelectList(driverList, "LastName", "LastName");
return View("EditJob", new Job());
}
I have that big block of duplicate code there that is bugging me. I know there will be a better solution, but I just don't know what it is. I now want to use this same set of drop downs on another view within this controller, so I'll be duplicating this code for a third time using my current method.
Any ideas? Maybe it's something obvious I'm totally overlooking... Thanks in advance for the help.
You can create a PartialView that accepts as it's model a view model that is designed to contain the data for those four drop down lists.
Then have some service method that returns that view, this may then be called from any controller and then either passed straight to your views, or added as a child object to another view model that then passes it down to your PartialView.
// a view model class to contain the SelectLists that will be rendered as drop downs
public class DropDownViewModel
{
public SelectList Customers{get;set;}
public SelectList VehicleReg{get;set;}
public SelectList VehicleType{get;set;}
public SelectList LastName{get;set;}
}
// another view model that contains a child DropDownViewModel
// object and also the Job object. This is what the Add and Edit Views
// will be responsible for rendering
public class JobsViewModel
{
public DropDownViewModel DropDownViewModel {get;set;}
public Job Job {get;set;}
}
// a service level class that will be responsible for constructing the
// DropDownViewModel object and populating with the required data
public class DropDownService
{
public DropDownViewModel GetDropDownViewModel()
{
// data access code
}
}
Then in your controllers
public ViewResult AddJob()
{
// get the view model
DropDownService service = new DropDownService();
DropDownViewModel dropDownViewModel = service.GetDropDownViewModel();
// create the wrapping JobViewModel that will contain the DropDownViewModel
JobViewModel viewModel= new JobViewModel();
viewModel.Job = new Job();
viewModel.DropDownViewModel = dropDownViewModel;
return View(viewModel);
}
public ViewResult EditJob(int jobId)
{
// get the view model
DropDownService service = new DropDownService();
DropDownViewModel dropDownViewModel = service.GetDropDownViewModel();
// create the wrapping JobViewModel that will contain the DropDownViewModel
JobViewModel viewModel= new JobViewModel();
viewModel.Job = jobsRepository.Jobs.First(x => x.JobID == jobId);
viewModel.DropDownViewModel = dropDownViewModel;
return View(viewModel);
}
In your mark up, you will need to ask your Add/Edit views to pass the model data down to the PartialView which you can do like this:
<% Html.RenderPartial("MyPartialView", Model.DropDownViewModel); "%>
What you're looking for is a ViewModel as described in the Nerd Dinner Tutorial. It's a custom, strongly typed representation of all the data that you need in your view including data to populate dropdowns. Swaffs answer is good and pretty complete. I just wanted to give you another reference. Good luck!

Using ViewModel Pattern with MVC 2 Strongly Typed HTML Helpers

I am working with ASP.NET MVC2 RC and can't figure out how to get the HTML helper, TextBoxfor to work with a ViewModel pattern. When used on an edit page the data is not saved when UpdateModel() is called in the controller. I have taken the following code examples from the NerdDinner application.
Edit.aspx
<%# Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NerdDinner.Models.DinnerFormViewModel>" %>
...
<p>
// This works when saving in controller (MVC 1)
<label for="Title">Dinner Title:</label>
<%= Html.TextBox("Title", Model.Dinner.Title) %>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
// This does not work when saving in the controller (MVC 2)
<label for="Title">Dinner Title:</label>
<%= Html.TextBoxFor(model => model.Dinner.Title) %>
<%= Html.ValidationMessageFor(model=> model.Dinner.Title) %>
</p>
DinnerController
// POST: /Dinners/Edit/5
[HttpPost, Authorize]
public ActionResult Edit(int id, FormCollection collection) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
return View(new DinnerFormViewModel(dinner));
}
}
When the original helper style is used (Http.TextBox) the UpdateModel(dinner) call works as expected and the new values are saved.
When the new (MVC2) helper style is used (Http.TextBoxFor) the UpdateModel(dinner) call does not update the values. Yes, the current values are loaded into the edit page on load.
Is there something else which I need to add to the controller code for it to work? The new helper works fine if I am just using a model and not a ViewModel pattern.
Thank you.
The issue here is your Edit form is using strongly typed helpers against a DinnerFormViewModel type, but you're calling UpdateModel on a Dinner type.
When you use the strongly typed helpers against the type, the helpers create form fields assuming that's the type you're posting to. When the types don't match up, there's a problem.
However, this is very easy to fix. You can provide a prefix to UpdateModel which indicates that you weren't trying to edit the whole model, you were trying to edit a property of the model, in this case a Dinner.
UpdateModel(dinner, "Dinner");
The other approach is to call UpdateModel on the actual ViewModel.
var viewModel = new DinnerFormViewModel();
viewModel.Dinner = repository.GetDinner(id);
UpdateModel(viewModel);
I think the first approach is much better.
On Page 90 in the "Wrox Professional ASP.NET MVC 2" book the code is listed as:
if (TryUpdateModel(dinner)) {
dinnerRepository.Save();
redirectToAction("Details", new { id=dinner.DinnerID });
But it should read:
if (TryUpdateModel(dinner, "Dinner")) {
dinnerRepository.Save();
redirectToAction("Details", new { id=dinner.DinnerID });
This method overload will try to update the specified model [Dinner], rather than the default [ViewModel], using the values from the controller's value provider. Basically all it does is add a prefix to all your values when looking them up in the provider.
So when the Model is looking to update its' Title property, it will look for Dinner.Title, instead of just Title in the controller's value provider.
While debugging, take a look in the Edit ActionResult method and inspect the FormCollection input param. When you dig down into it's entry array, you'll find Keys that all start with the prefix of the property object you referenced in your View, in your case the Edit View, like this:
<%: Html.TextBoxFor(model => model.Dinner.Title, new {size=50, #class="prettyForm" })%>
I'm not 100% sure, but it seems that strongly typed helper creates ids/names "Dinner.Title" instead of just "Title" and therefore - UpdateModel can't bind it.
Unfortunately - i haven't used UpdateModel method myself so i don't know the solution.
Could you add html that gets rendered for both approaches?
Playing around with reflector ATM.
This is what i found:
protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IValueProvider valueProvider) where TModel: class
{
if (model == null)
{
throw new ArgumentNullException("model");
}
if (valueProvider == null)
{
throw new ArgumentNullException("valueProvider");
}
Predicate<string> predicate = delegate (string propertyName) {
return BindAttribute.IsPropertyAllowed(propertyName, base.includeProperties, base.excludeProperties);
};
IModelBinder binder = this.Binders.GetBinder(typeof(TModel));
ModelBindingContext context2 = new ModelBindingContext();
context2.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(delegate {
return base.model;
}, typeof(TModel));
context2.ModelName = prefix;
context2.ModelState = this.ModelState;
context2.PropertyFilter = predicate;
context2.ValueProvider = valueProvider;
ModelBindingContext bindingContext = context2;
binder.BindModel(base.ControllerContext, bindingContext);
return this.ModelState.IsValid;
}
Parameters
- model The model instance to update.
- prefix The prefix to use when looking up values in the value provider.
So - you can try to use UpdateModel<T>(T model, string prefix) overload and pass "Dinner" or "Dinner." as prefix argument.
Maybe a simpler way to put this is as follows. If you are cutting and pasting code from the wrox download for the NerDDinner tutorial, you'll find there are some errors. Using a suggestion from above, I modified the example from 1-53.txt to get this to work. The change follows:
//
// POST: /Dinners/Edit/2
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
// Retrieve existing dinner
Dinner dinner = dinnerRepository.GetDinner(id);
DinnerFormViewModel viewModel = new DinnerFormViewModel(dinner);
if (TryUpdateModel(viewModel))
{
// Persist changes back to database
dinnerRepository.Save();
// Perform HTTP redirect to details page for the saved Dinner
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
else
{
return View(viewModel);
}
}
A simpler way is using the prefix as parameter name, just do like this:
public ActionResult Edit(Dinner Dinner, int DinnerID)
{
...
}

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.

Resources