How to get the Model from an ActionResult? - asp.net-mvc

I'm writing a unit test and I call an action method like this
var result = controller.Action(123);
result is ActionResult and I need to get the model somehow, anybody knows how to do this?

In my version of ASP.NET MVC there is no Action method on Controller. However, if you meant the View method, here's how you can unit test that the result contains the correct model.
First of all, if you only return ViewResult from a particular Action, declare the method as returning ViewResult instead of ActionResult.
As an example, consider this Index action
public ViewResult Index()
{
return this.View(this.userViewModelService.GetUsers());
}
you can get to the model as easily as this
var result = sut.Index().ViewData.Model;
If your method signature's return type is ActionResult instead of ViewResult, you will need to cast it to ViewResult first.

We placed the following piece in a testsbase.cs allowing for typed models in the tests a la
ActionResult actionResult = ContextGet<ActionResult>();
var model = ModelFromActionResult<SomeViewModelClass>(actionResult);
ModelFromActionResult...
public T ModelFromActionResult<T>(ActionResult actionResult)
{
object model;
if (actionResult.GetType() == typeof(ViewResult))
{
ViewResult viewResult = (ViewResult)actionResult;
model = viewResult.Model;
}
else if (actionResult.GetType() == typeof(PartialViewResult))
{
PartialViewResult partialViewResult = (PartialViewResult)actionResult;
model = partialViewResult.Model;
}
else
{
throw new InvalidOperationException(string.Format("Actionresult of type {0} is not supported by ModelFromResult extractor.", actionResult.GetType()));
}
T typedModel = (T)model;
return typedModel;
}
An example using a Index page and List:
var actionResult = controller.Index();
var model = ModelFromActionResult<List<TheModel>>((ActionResult)actionResult.Result);

consider a = ActionResult;
ViewResult p = (ViewResult)a;
p.ViewData.Model

It's somewhat cheating but a very trivial way to do so in .NET4
dynamic result = controller.Action(123);
result.Model
Used this today in a unit test. Might be worth some sanity checks such as:
Assert.IsType<ViewResult>(result);
Assert.IsType<MyModel>(result.Model);
Assert.Equal(123, result.Model.Id);
You could skip the first one if the result is going to be a view or partial result depending on the input.

Related

MVC3 Razor ViewBag.Model not making into the Page

I have modified a Model recently for a system I've inherited, and for some reason the ViewBag.Model is not making into the page. I've reverted the code back to before I started tinkering, and still no luck.
The call into the view is as follows:
public virtual ActionResult Edit(long id)
{
var _news = _newsRepository.GetNewsById(id);
ViewBag.Model = AutoMapper.Mapper.Map<News, NewsModel>(_news);
ViewBag.Model.CurrentNewsImageFile = ConfigSettings.HostDomainName + ConfigSettings.NewsImageBasePath + _news.image_file;
return View();
}
The view has the following code:
#model MyModels.Models.NewsModel
#{
bool IsCreate = Model == null || Model.Id == 0;
ViewBag.Title = IsCreate ? "Add News" : "Edit News";
}
The problem is that "Model" is always null in the view code... what have I missed? Am I missing a fundamental here?
When tracing through the ActionResult code, right up until the return View() the debug inspector correctly shows the Model containing everything I would expect it too.
You need to simply return View(theModel). If you don't pass the model into View() it doesn't know what to do.
However, I'm confused about the code you have listed, you seem to be trying to put the model into the ViewBag? The ViewBag shouldn't contain the model.
I think the code you want is:
public virtual ActionResult Edit(long id)
{
var _news = _newsRepository.GetNewsById(id);
var model = AutoMapper.Mapper.Map<News, NewsModel>(_news);
model.CurrentNewsImageFile = ConfigSettings.HostDomainName + ConfigSettings.NewsImageBasePath + _news.image_file;
return View(model);
}
I think it is the AutoMapper line. Once you have created your mappings I believe you need to them call them using the following syntax:
var newsModel = Mapper.Map(_news);

Controller searching for view instead of returning a different view method

I have two controller actions outlined below:
public ViewResult TitleStorylines(int id)
{
var vm = Service.Get(id);
vm.IsEditable = User.HasPermission(SecurityPermissionType.ManageStorylines);
return View(vm);
}
public ViewResult TitleStorylinesCreate(TitleStorylineModel model)
{
var created = Service.Create(model);
return TitleStorylines(created.TitleId);
}
I only have one view in my project, called TitleStorylines, which the first controller action handles fine. But when I call the second method, it gives me an error saying that it can't find the view called TitleStorylinesCreate even though I'm explicitly calling the previous method. What gives?
Did you try ?
return View("TitleStorylines",created.TitleId);
EDIT: Based on your update : I guess you are posting your form back the TitleStorylinesCreate. So probably after saving, dont you want to redirect the user back to the Get action of same ?
[HttpPost]
public ViewResult TitleStorylinesCreate(TitleStorylineModel model)
{
var created = Service.Create(model);
return RedirectToAction("TitleStorylines",new { #id=created.TitleId});
}
In the above example we are doing the PRG pattern. Post -> Redirect -> Get
After saving, we are redirecting them back to the first method. It will be a HTTP GET method.
public ActionResult TitleStorylinesCreate(TitleStorylineModel model)
{
var created = Service.Create(model);
return RedirectToAction("TitleStorylines",new { #id=created.TitleId});
}

Render different view depending on the type of the data (asp.net MVC)

So lets suppose we have an action in a controller that looks a bit like this:
public ViewResult SomeAction(int id)
{
var data = _someService.GetData(id);
...
//create new view model based on the data here
return View(viewModel);
}
What I m trying to figure out is the best way to render a diferent view based on the type fo the data.
the "_someService.GetData method returns an data that knows its out type (ie not only you can do typeof(data) but also you can do data.DataType and you ll get an enum value
so I could achieve what I m trying to do doing something kinda like this
public ViewResult SomeAction(int id)
{
var data = _someService.GetData(id);
//mapping fields to the viewModel here
var viewModel = GetViewModel(data);
swtich(data.DataType)
case DataType.TypeOne: return View("TypeOne", viewModel); break;
...
}
But this does not seem to be the nicest way, (I dont event know if it would work)
Is this the way to go?
Should I use some sort of RenderPartial Aproach? after all , waht will change in the view is mostly the order of the data (ie the rest of the view would be quite similar)
Cheers
Try this:
public ViewResult SomeAction(int id)
{
var data = _someService.GetData(id);
var viewModel = GetViewModel(data);
return View(data.GetType().Name, viewModel);
}
Then just name your views accordingly.

How do I unit test a controller method that has the [Authorize] attribute applied?

I've searched stackoverflow and googled four a couple of hours and still not found any solution for my "trivial" problem.
If you write unit test for your filtered [Authorize] ActionResult, how do you solve the problem to fake that user is authenticated?
I have a lot of ActionResult methods that are filtered with [Authorize] and I want to test all of my ActionResult methods regardless if they are filtered with [Authorize] or not.
A simple example of what i mean:
[TestMethod]
public void Create_Get_ReturnsView()
{
// Arrange
var controller = new UserController();
// Act
var result = controller.Create();
// Assert
Assert.IsNotNull(result as ViewResult);
}
[Authorize]
public ActionResult Create()
{
return View("Create");
}
As of now the tests don't even hit the ActionResult method because of the [Authorize] filter, exception thrown is: System.NullReferenceException: Object reference not set to an instance of an object.
You need to mock a context for your controller. Try using Moq
Your arrange would then look like:
var controller = new UserController();
var mock = new Mock<ControllerContext>();
mock.SetupGet(x => x.HttpContext.User.Identity.Name).Returns("SOMEUSER");
mock.SetupGet(x => x.HttpContext.Request.IsAuthenticated).Returns(true);
controller.ControllerContext = mock.Object;
You should be able to then do your Act & Assert.
If you haven't already, I would highly recommend looking through NerdDinner as an example MVC site.

Unit testing my controller method results in an empty ViewName?

I'm doing some simple MS unit tests on my standard, nothing special controller.
When I check the ViewName proprty, from the returned ViewResult object, it's "" (empty).
I'm under the impression that the ViewName is implied by the name of the View (as suggested by this MS article on ASP.NET MVC controller testing).
BTW, when I test the ViewData, it's all there and correct.
Here's the code I have...
public ActionResult Index(int? page, string tag)
{
if (page == null || page <= 0)
{
page = 1;
}
var viewData = new IndexViewData
{
... my property setters, etc ...
};
return View(viewData);
}
[TestMethod]
public void Index_Action_Should_Return_Index_View_For_Default_HomePage()
{
// Arrange.
var controller = PostController; // Wrapper, cause I use D.I.
// Act.
ViewResult viewResult = controller.Index(null, null) as ViewResult;
// Assert.
Assert.IsNotNull(viewResult);
Assert.AreEqual("Index", viewResult.ViewName); // This is false/fails.
var indexViewData = viewResult.ViewData.Model as IndexViewData;
Assert.IsNotNull(indexViewData); // This is true.
}
The ViewName is only present when you set it in the ViewResult. If your View name matches your controller name, then I would check to ensure that the ViewName is null or empty as that would be (IMO) the correct behavior since you wouldn't want to set a name on the view. I only check that the ViewName is set when I intend that the View to be returned does not match the action -- say, when returning the "Error" view, for example.
EDIT: The following is the source for ExecuteResult in ViewResultBase.cs (from RC1, I don't have the source for RTW on my Macintosh). As you can see it checks to see if the ViewName has been set directly and if not, it pulls it from the action in the controller context's route data. This only happens in ExecuteResult, which is invoked AFTER your controller's action has completed.
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
if (String.IsNullOrEmpty(ViewName)) {
ViewName = context.RouteData.GetRequiredString("action");
}
ViewEngineResult result = null;
if (View == null) {
result = FindView(context);
View = result.View;
}
ViewContext viewContext = new ViewContext(context, View, ViewData, TempData);
View.Render(viewContext, context.HttpContext.Response.Output);
if (result != null) {
result.ViewEngine.ReleaseView(context, View);
}
}
I personally found the testing facilities provided by MVC2 to be somewhat clumsy. I'm guessing there is something better already extant, but I ended up creating a simple class to test actions. I modeled the interface (the implementation is another story) on a class provided by the excellent open source Java MVC framework Stripes called MockRoundTrip.
Here is the method used to get the action destination page when testing actions, called getTripDestination(). It returns the correct result irrespective of whether the viewname is explicitly set or not
//Get the destination page of the request, using Runtime execution pattern of MVC, namely
//if no ViewName is explicitly set in controller, ViewResult will have an empty ViewName
//Instead, current action name will be used in its place
public string getTripDestination()
{
RouteData routeData = getRouteData();
ViewResult viewResult = (result is ViewResult) ? (ViewResult)result : null;
string tripDestination = (viewResult != null) ? viewResult.ViewName : "";
return (tripDestination != "") ? tripDestination : (String)routeData.Values["action"];
}
private RouteData getRouteData()
{
HttpContextBase context = controller.ControllerContext.RequestContext.HttpContext;
return RouteTable.Routes.GetRouteData(context);
}
The viewname is set automatically by the framework. But when we unit test, we short-circuit the framework and there is nothing left to set the name.
So our actions need to set the viewname explicitly when we unit test. We could also check for null or empty if we really, really want to lean on the convention.
The documentation for Controller.View() states:
This method overload of the View class returns a ViewResult object
that has an empty ViewName property. If you are writing unit tests for
controller actions, take into account the empty ViewName property for
unit tests that do not take a string view name.
At run time, if the ViewName property is empty, the current action
name is used in place of the ViewName property.
So when expecting a view with the same name as the current action we can just test that it's an empty string.
Alternatively, the Controller.View(String) method will set the ViewName.

Resources