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.
Related
In ASP.NET MVC 5, I'm trying to extend my site's general Error Handling and I'm having trouble to cover ActionResult and JsonResult in once. I'm aware that JsonResult is divered from ActionResult, but when I return ActionResult I just give MVC a View, and for JsonResult, I handle the result with Javascript.
So the case, where the return value for the Action is an ActionResult, and the Application_Error() returns a JsonResult, the Json with just be printed in the view. What are you supposed to do in that situation? I rather have it to redirect to errorpage or login screen. I don't even know if I'm supossed to craft so javascript that handle the catch of the json to manually redirect or there is some "built-in" MVC mechanisme I should use.
In a simplified example I have the two Action's:
public ActionResult ShowPackages(PackagesViewModel packagesViewModel)
{
var model = PackageBalhelper.GetPackageViewModel(packagesViewModel);
return View(model);
}
[HttpPost]
public JsonResult GetDatatableRows(PackagesViewModel packagesViewModel)
{
var jsonResult = CreateJsonResult(packagesViewModel);
return jsonResult;
}
What I've so far (From MSDN)
protected void Application_Error()
{
HttpContext httpContext = HttpContext.Current;
if (httpContext != null)
{
var requestContext = ((MvcHandler)httpContext.CurrentHandler).RequestContext;
// When the request is ajax the system can automatically handle a mistake with a JSON response. Then overwrites the default response
if (requestContext.HttpContext.Request.IsAjaxRequest())
{
httpContext.Response.Clear();
string controllerName = requestContext.RouteData.GetRequiredString("controller");
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(requestContext, controllerName);
ControllerContext controllerContext = new ControllerContext(requestContext, (ControllerBase)controller);
var jsonResult = new JsonResult
{
Data = new
{
success = false,
serverError = "500"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
jsonResult.ExecuteResult(controllerContext);
httpContext.Response.End();
}
else
{
LogHelper.WriteMessageToLog("In 'Application_Error'. Not an ajax request'.");
//httpContext.Response.Redirect("~/Error");
}
}
}
What are you supposed to do in that situation? I rather have it to
redirect to errorpage or login screen.
You are on the right track. We definitely do not want to return View (redirect to custom error page), if the client expects Json response from server. Otherwise, it will mess up the client's logic.
This answer might not answer your question directly. However, if you see yourself returning JsonResult a lot, you might want to consider using Web API Controller in ASP.NET MVC 5.
Web API 2.1 supports GlobalExceptionHandler in which you can customize the Http response that is sent when an unhanded application expcetion occurs.
In my case, I use Angular with ASP.NET MVC and Web API. So, I have to return unhandled exception in Json format for Ajax requests.
WebApiExceptionHandler.cs
If your application throws custom exception, you can even filter them here, and return appropriate message.
public class WebApiExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var exception = context.Exception;
var httpException = exception as HttpException;
if (httpException != null)
{
context.Result = new WebApiErrorResult(context.Request,
(HttpStatusCode) httpException.GetHttpCode(), httpException.Message);
return;
}
/*if (exception is MyCustomException)
{
context.Result = new WebApiErrorResult(context.Request,
HttpStatusCode.NotFound, exception.Message);
return;
}*/
context.Result = new WebApiErrorResult(context.Request,
HttpStatusCode.InternalServerError,
"An error occurred while processing your request.");
}
}
WebApiErrorResult.cs
public class WebApiErrorResult : IHttpActionResult
{
private readonly string _errorMessage;
private readonly HttpRequestMessage _requestMessage;
private readonly HttpStatusCode _statusCode;
public WebApiErrorResult(
HttpRequestMessage requestMessage,
HttpStatusCode statusCode,
string errorMessage)
{
_requestMessage = requestMessage;
_statusCode = statusCode;
_errorMessage = errorMessage;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_requestMessage.CreateErrorResponse(_statusCode, _errorMessage));
}
}
WebApiConfig.cs
Finally, we register our custom exception handler with framework.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
config.Services.Replace(typeof(IExceptionHandler), new WebApiExceptionHandler());
config.Filters.Add(new AuthorizeAttribute());
}
}
I'm tinkering with an MVC Controller, having lifted some code out of an ApiController:
public class LoginController : Controller
{
public async Task<IHttpActionResult> Login(LoginModel model)
{
var success = await _accountClient.Login(model);
if (!success)
{
return new UnauthorizedResult(Enumerable.Empty<AuthenticationHeaderValue>(), new HttpRequestMessage());
}
return new OkResult(new HttpRequestMessage());
}
}
This just doesn't look or feel right, particularly the part where I'm creating a new HttpRequestMessage; I'm pretty sure that should somehow come from the incoming Request, but I can't figure out where that's supposed to come from.
Also, how do you create the IEnumerable<AuthenticationHeaderValue> object for the challenges parameter to the UnauthorizedResult constructor?
You can add headers to your Response object as
Response.AddHeader("Token","your-generated-token");
Also you can change your action method as
public async Task<HttpStatusCodeResult> Login(LoginModel model)
{
var success = await _accountClient.Login(model);
if (!success)
{
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
}
return new HttpStatusCodeResult(HttpStatusCode.Ok);
}
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.
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.
I can't find a solution to mock ControllerContext.ParentActionViewContext.
here is the code of my controller
[ChildActionOnly]
public ViewResult Menu()
{
string controller = ControllerContext.ParentActionViewContext.RouteData.Values["controller"].ToString();
string action = ControllerContext.ParentActionViewContext.RouteData.Values["action"].ToString();
List menuItems = new List();
...code to populate my list...
return View(menuItems);
}
What I want to accomplish is mock ParentActionViewContext in a test so i can pass whatever controller and action I want to do my simulations.
I can mock the RouteData of the ControllerContext, but I can't fake the one of the parent controller.
Maybe I'm missing something obviuos.
Any help is greatly appreciated.
You're not missing anything obvious. You already discovered that the ParentActionViewContext property of the ControllerContext is not marked virtual and therefore, un-mockable. You can however, accomplish what you want by creating a ViewContext object with the values you want, adding that object to the RouteData.DataTokens dictionary with the key "ParentActionViewContext."
You can view the source code to the ControllerContext class and the implementation of the ParentActionViewContext property at http://bit.ly/ku8vR4.
Here's how I implemented this in my test:
[TestFixture]
public class SomeControllerTests
{
private PartialViewResult _result;
private Mock<HttpContextBase> _mockHttpContext;
private HttpContextBase _httpContext;
private RouteData _routeData;
private RouteData _parentRouteData;
[Test]
public void CanDoSomething()
{
SetupAnonymousUser();
SetupHttpContext();
SetupRouteData();
var controller = new FooController();
controller.ControllerContext = new ControllerContext(_httpContext, _routeData, controller);
_result = controller.Index() as PartialViewResult;
var model = _result.ViewData.Model as FooViewModel;
Assert.IsNotNull(model);
Assert.AreEqual("New", model.UserStatus);
Assert.AreEqual("21", model.PromoId);
}
private void SetupHttpContext()
{
_mockHttpContext = new Mock<HttpContextBase>();
_httpContext = _mockHttpContext.Object;
}
private void SetupRouteData()
{
SetupParentRouteData();
var viewContext = new ViewContext {RouteData = _parentRouteData};
_routeData = new RouteData();
_routeData.Values.Add("controller", "foo");
_routeData.Values.Add("action", "index");
_routeData.DataTokens["ParentActionViewContext"] = viewContext;
}
private void SetupParentRouteData()
{
_parentRouteData = new RouteData();
_parentRouteData.Values.Add("controller", "home");
_parentRouteData.Values.Add("action", "index");
}
}
Hope this helps!
Michael Ibarra