I have a method on MVC5 controller:-
[HttpPost]
public JsonResult Save(TaskViewModel model)
{
if (ModelState.IsValid)
{
var task = model.ToTask();
_taskService.Save(task);
return Json(task);
}
return Json(new ErrorViewModel(ModelState));
}
Which I am happily unit testing like so:-
[Test]
public void Save_WhenInvalidModel_ThenDoNotCallITaskServiceSave()
{
var model = new TaskViewModel();
var service = new Mock<ITaskService>();
service.Setup(m => m.Save(It.IsAny<Task>()));
var controller = CreateController(service.Object);
ValidateModel(model, controller);
controller.Save(model);
service.Verify(f => f.Save(It.IsAny<Task>()), Times.Never());
}
protected static void ValidateModel(object model, Controller controller)
{
var validationContext = new ValidationContext(model, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(model, validationContext, validationResults);
foreach (var validationResult in validationResults)
{
controller.ModelState.AddModelError(validationResult.MemberNames.First(), validationResult.ErrorMessage);
}
}
However, I need to make this method return a 400 status code and return content. So I made the following change to the controller method.
[HttpPost]
public JsonResult Save(TaskViewModel model)
{
if (ModelState.IsValid)
{
var task = model.ToTask();
_taskService.Save(task);
return Json(task);
}
Response.StatusCode = 400;
Response.TrySkipIisCustomErrors = true;
return Json(new ErrorViewModel(ModelState));
}
This causes my unit test to fail with a System.NullReferenceException because Response does not exist.
So my question is what is the best practice here to both make this controller testable and also to verify the status code value?
UPDATE
While this is very similar to MVC3 unit testing response code it does not address that I am trying to return JSON content as well as a 400 status code
Have a look at this answer. It explains in detail how to test using HttpContext and Response.
Related
I want to know, there is any technique so we can pass Model as a parameter in RedirectToAction
For Example:
public class Student{
public int Id{get;set;}
public string Name{get;set;}
}
Controller
public class StudentController : Controller
{
public ActionResult FillStudent()
{
return View();
}
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return RedirectToAction("GetStudent","Student",new{student=student1});
}
public ActionResult GetStudent(Student student)
{
return View();
}
}
My Question - Can I pass student model in RedirectToAction?
Using TempData
Represents a set of data that persists only from one request to the
next
[HttpPost]
public ActionResult FillStudent(Student student1)
{
TempData["student"]= new Student();
return RedirectToAction("GetStudent","Student");
}
[HttpGet]
public ActionResult GetStudent(Student passedStd)
{
Student std=(Student)TempData["student"];
return View();
}
Alternative way
Pass the data using Query string
return RedirectToAction("GetStudent","Student", new {Name="John", Class="clsz"});
This will generate a GET Request like Student/GetStudent?Name=John & Class=clsz
Ensure the method you want to redirect to is decorated with [HttpGet] as
the above RedirectToAction will issue GET Request with http status
code 302 Found (common way of performing url redirect)
Just call the action no need for redirect to action or the new keyword for model.
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return GetStudent(student1); //this will also work
}
public ActionResult GetStudent(Student student)
{
return View(student);
}
Yes you can pass the model that you have shown using
return RedirectToAction("GetStudent", "Student", student1 );
assuming student1 is an instance of Student
which will generate the following url (assuming your using the default routes and the value of student1 are ID=4 and Name="Amit")
.../Student/GetStudent/4?Name=Amit
Internally the RedirectToAction() method builds a RouteValueDictionary by using the .ToString() value of each property in the model. However, binding will only work if all the properties in the model are simple properties and it fails if any properties are complex objects or collections because the method does not use recursion. If for example, Student contained a property List<string> Subjects, then that property would result in a query string value of
....&Subjects=System.Collections.Generic.List'1[System.String]
and binding would fail and that property would be null
[HttpPost]
public async Task<ActionResult> Capture(string imageData)
{
if (imageData.Length > 0)
{
var imageBytes = Convert.FromBase64String(imageData);
using (var stream = new MemoryStream(imageBytes))
{
var result = (JsonResult)await IdentifyFace(stream);
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(serializer.Serialize(result.Data));
if (faceRecon.Success) return RedirectToAction("Index", "Auth", new { param = serializer.Serialize(result.Data) });
}
}
return Json(new { success = false, responseText = "Der opstod en fejl - Intet billede, manglede data." }, JsonRequestBehavior.AllowGet);
}
// GET: Auth
[HttpGet]
public ActionResult Index(string param)
{
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(param);
return View(faceRecon);
}
[NonAction]
private ActionResult CRUD(someModel entity)
{
try
{
//you business logic here
return View(entity);
}
catch (Exception exp)
{
ModelState.AddModelError("", exp.InnerException.Message);
Response.StatusCode = 350;
return someerrohandilingactionresult(entity, actionType);
}
//Retrun appropriate message or redirect to proper action
return RedirectToAction("Index");
}
i did find something like this, helps get rid of hardcoded tempdata tags
public class AccountController : Controller
{
[HttpGet]
public ActionResult Index(IndexPresentationModel model)
{
return View(model);
}
[HttpPost]
public ActionResult Save(SaveUpdateModel model)
{
// save the information
var presentationModel = new IndexPresentationModel();
presentationModel.Message = model.Message;
return this.RedirectToAction(c => c.Index(presentationModel));
}
}
Short story about what I am doing and why. I had been doing view to string conversion in Mvc project, but suddenly all project moved to the REST API. But I had to use razor engine to convert my view with all model data there, so I was trying to use directly from api, but it didn't work for me, so I decided to create a Mvc controller and call it directly from API, what is missing, only ControllerContext, because when I create controller directly, it appears to be null.
Here is my Api controller
public class AttachmentController : ApiController
{
[HttpGet]
public async Task<IHttpActionResult> Get(long id)
{
try
{
var mvcController = new AttachmentMvcController();
var result = await mvcController.Get();
return Ok(result);
}
catch (Exception e)
{
return InternalServerError(e);
}
}
}
and this is my Mvc Controller
public class AttachmentMvcController : Controller
{
public AttachmentMvcController(){ }
public async Task<byte[]> Get()
{
string result;
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// HERE IS MY PROBLEM, I NEED this.ControllerContext, but it is null !!!
if (ControllerContext == null)
{
// create it and do task
var factory = DependencyResolver.Current.GetService<IControllerFactory>() ?? new DefaultControllerFactory();
AttachmentMvcController controller = factory.CreateController(ctx.RequestContext, "AttachmentMvc") as AttachmentMvcController;
RouteData route = new RouteData();
route.Values.Add("action", "ActionThatUsesControllerContext"); // ActionName, but it not required
ControllerContext newContext = new ControllerContext(new HttpContextWrapper(System.Web.HttpContext.Current), route, controller);
controller.ControllerContext = newContext;
result = await controller.ActionThatUsesControllerContext(id);
}
else
{
result = await this.ActionThatUsesControllerContext(id);
}
return result;
}
private async Task<byte[]> ActionThatUsesControllerContext()
{
{....}
// here I am trying to use helper that uses that controller context
string viewAsString = ControllerContext.RenderPartialToString("MyView", requestModel);
{....}
}
}
If anyone has idea how to get that ControllerContext or any other ways to get my razor engine render view inside ApiController, please share.
I have a controller in which I am unit testing my Index action. I am having problem in unit testing User.Identity.GetUserId()
This is my controller
public ActionResult Index()
{
string userId = User.Identity.GetUserId();
DemoModel demoModel = _demoModelService.GetByUserId(userId);
MyModel myModel = new MyModel()
{
Name = demoModel.Name;
Description = demoModel.Description;
}
return View(myModel);
}
This is my Unit Test:
public void Test_Index_Action()
{
//Act
var result = controller.Index() as ViewResult;
//Assert
Assert.AreEqual("", result.ViewName);
}
When I debug my test method, as it reaches the first line of code(User.Identity.GetUserId) of my Index action, it generates null UserId. How can I access the userId in unit testing this code?
I've been struggeling with mvc unit test my self, while there are known techniques to improve testability of your mvc application, most of the projects I worked on are sadly not following them.
So I decided to start this project to help me and others who love to unit test their mvc application. Please take a look at: https://github.com/ibrahimbensalah/Xania.AspNet.Simulator.
Here is an example unit test class
using System.Web.Mvc;
using NUnit.Framework;
using Xania.AspNet.Simulator;
public class SimulatorTests
{
[Test]
public void ActionExecuteTest()
{
// arange
var controller = new TestController();
// act
var result = controller.Execute(c => c.Index());
// assert
Assert.AreEqual("Hello Simulator!", result.ViewBag.Title);
}
[Test]
public void UnAuthorizedActionTest()
{
// arrange
var controller = new TestController();
// act
var result = controller.Execute(c => c.UserProfile());
// assert
Assert.IsInstanceOf<HttpUnauthorizedResult>(result.ActionResult);
}
[Test]
public void AuthorizedActionTest()
{
// arrange
var controller = new TestController();
// act
var result = controller.Action(c => c.UserProfile()).Authenticate("user", null).Execute();
// assert
Assert.IsInstanceOf<ViewResult>(result.ActionResult);
}
}
public class TestController : Controller
{
public ActionResult Index()
{
ViewBag.Title = "Hello Simulator!";
return View();
}
[Authorize]
public ActionResult UserProfile()
{
return View();
}
}
There is a simple controller that a querystring is read in constructor of it.
public class ProductController : Controller
{
parivate string productName;
public ProductController()
{
productName = Request.QueryString["productname"];
}
public ActionResult Index()
{
ViewData["Message"] = productName;
return View();
}
}
Also I have a function in unit test that create an instance of this Controller and I fill the querystring by a Mock object like below.
[TestClass]
public class ProductControllerTest
{
[TestMethod]
public void test()
{
// Arrange
var querystring = new System.Collections.Specialized.NameValueCollection { { "productname", "sampleproduct"} };
var mock = new Mock<ControllerContext>();
mock.SetupGet(p => p.HttpContext.Request.QueryString).Returns(querystring);
var controller = new ProductController();
controller.ControllerContext = mock.Object;
// Act
var result = controller.Index() as ViewResult;
// Assert
Assert.AreEqual("Index", result.ViewName);
}
}
Unfortunately Request.QueryString["productname"] is null in constructor of ProductController when I run test unit.
Is ther any way to fill a querystrin by a mocking and get it in constructor of a control?
There is a simple controller that a querystring is read in constructor of it.
You shouldn't be doing this and such controller shouldn't exist. The controller context is not yet initialized in the constructor and it will fail not only for the unit test but in real.
You should use the Initialize method where you have access to the request context.
Hello i am creating a small webpage that give our users an interface to search in our database.
This website must supply 2 webservice functions, Search(string searchString) and GetAccountInfoByInitials(string initials)
I would like to use my controllers to do this, but i can not find out how i can get the html out of a ViewResult.
I have tryed the following, but the result.ToString() only give me the string "System.Web.Mvc.ViewResult"
public class SearchService : ISearchService
{
private readonly ServiceHandlerController _controller;
public SearchService()
{
_controller = new ServiceHandlerController();
}
public string Search(string searchString)
{
var result = _controller.Search(searchString);
return result.ToString();
}
public string GetAccountInfoByInitials(string initials)
{
var result = _controller.Search(initials).ToString();
return result;
}
}
This is an answer to a question I posted similar to this one:
Is there a way to process an MVC view (aspx file) from a non-web application?
public class AspHost : MarshalByRefObject
{
public string _VirtualDir;
public string _PhysicalDir;
public string ViewToString<T>(string aspx, Dictionary<string, object> viewData, T model)
{
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter tw = new HtmlTextWriter(sw))
{
var workerRequest = new SimpleWorkerRequest(aspx, "", tw);
HttpContext.Current = new HttpContext(workerRequest);
ViewDataDictionary<T> viewDataDictionary = new ViewDataDictionary<T>(model);
foreach (KeyValuePair<string, object> pair in viewData)
{
viewDataDictionary.Add(pair.Key, pair.Value);
}
object view = BuildManager.CreateInstanceFromVirtualPath(aspx, typeof(object));
ViewPage viewPage = view as ViewPage;
if (viewPage != null)
{
viewPage.ViewData = viewDataDictionary;
}
else
{
ViewUserControl viewUserControl = view as ViewUserControl;
if (viewUserControl != null)
{
viewPage = new ViewPage();
viewPage.Controls.Add(viewUserControl);
}
}
if (viewPage != null)
{
HttpContext.Current.Server.Execute(viewPage, tw, true);
return sb.ToString();
}
throw new InvalidOperationException();
}
}
}
public static AspHost SetupFakeHttpContext(string physicalDir, string virtualDir)
{
return (AspHost)ApplicationHost.CreateApplicationHost(
typeof(AspHost), virtualDir, physicalDir);
}
}
Then, to render a file:
var host = AspHost.SetupFakeHttpContext("Path/To/Your/MvcApplication", "/");
var viewData = new ViewDataDictionary<SomeModelType>(){ Model = myModel };
String rendered = host.ViewToString("~/Views/MyView.aspx", new Dictionary<string, object>(viewData), viewData.Model);
I do this in almost an identical fashion but actually use the controller iteself to return a partialview to string.
i use the following extension method in my base controller:
public static class ExtensionMethods
{
public static string RenderPartialToString(this ControllerBase controller, string partialName, object model)
{
var vd = new ViewDataDictionary(controller.ViewData);
var vp = new ViewPage
{
ViewData = vd,
ViewContext = new ViewContext(),
Url = new UrlHelper(controller.ControllerContext.RequestContext)
};
ViewEngineResult result = ViewEngines
.Engines
.FindPartialView(controller.ControllerContext, partialName);
if (result.View == null)
{
throw new InvalidOperationException(
string.Format("The partial view '{0}' could not be found", partialName));
}
var partialPath = ((WebFormView)result.View).ViewPath;
vp.ViewData.Model = model;
Control control = vp.LoadControl(partialPath);
vp.Controls.Add(control);
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
using (var tw = new HtmlTextWriter(sw))
{
vp.RenderControl(tw);
}
}
return sb.ToString();
}
}
followed by then returning my partialview in the following fashion:
return Content(this.RenderPartialToString("myPartialView", myModel));
This should hopefully sort you out.
The View Result doesn't holds the page by itself. Asp.net MVC uses it, along with the View Engine configured to get the actual page.
You'll hit a roadblock if you are using the default view engine - see link text. Basically because the asp.net view engine is tied to the context. Other view engines won't give you this issue, but if the assets you are trying to reduce are already relying in the default view engine then that defeats the purpose. There are other ways around it, but I'm unsure how convenient those would be for you.