I would like to retrieve the instance of ActionExecutingContext inside of
public ActionResult Contact2(string one, string two)
and not in the class albumAttribute.
Is it possible to do it?
Thanks!
[HttpPost]
[album]
public ActionResult Contact2(string one, string two)
{
ViewBag.Message = "Your contact page.";
var ss = Response.Status;
var genres = new List<Genre>
{
new Genre { Name = "Disco"},
new Genre { Name = "Jazz"},
new Genre { Name = "Rock"}
};
//return View(genres);
//return View("contact2", genres);
return View("contact22", genres);
}
public class albumAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase req = filterContext.HttpContext.Request;
HttpResponseBase res = filterContext.HttpContext.Response;
UriBuilder uriBuilder = new UriBuilder("http://" + req.Url.Authority + req.Url.LocalPath);
NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
query.Add("album", "first");
uriBuilder.Query = query.ToString();
string url = req.Url.AbsolutePath.ToString();
res.Redirect(uriBuilder.Uri.OriginalString);
base.OnActionExecuting(filterContext);
/*
UriBuilder uriBuilder = new UriBuilder("http://" + req.Url.Authority + "/Home/About");
res.Redirect(uriBuilder.Uri.OriginalString);
base.OnActionExecuting(filterContext);
*/
}
}
Based on your comments:
Action filters execute prior to Actions so inside an Action you won't be able to use base.OnActionExecuting(filterContext).
Other than that all the code that's attached in the image could be executed without ActionExecutingContext object, just add it to your Action and for getting a Request and Response objects use Response and Request controller properties.
You can also use
return this.Redirect(yourUrl);
instead of res.Redirect(...)
[HttpPost]
[album]
public ActionResult Contact2(string one, string two)
{
var req = this.Request;
var res = this.Response;
UriBuilder uriBuilder = new UriBuilder("http://" + req.Url.Authority + req.Url.LocalPath);
NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
query.Add("album", "first");
uriBuilder.Query = query.ToString();
string url = req.Url.AbsolutePath.ToString();
return this.Redirect(uriBuilder.Uri.OriginalString);
}
Related
I am using a custom HandleErrorAttribute which is applied globally by being registered in the filters. The problem is, I can't catch the proper http error code. Every time it is 200 code inside that attribute.
public class HandleAndLogErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
return;
var statusCode = filterContext.HttpContext.Response.StatusCode; //always 200
LogManager.Error(filterContext.Exception);
filterContext.Result = CreateActionResult(filterContext, statusCode);
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
//filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
var ctx = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode)statusCode).ToString();
var viewName = "~/Views/Shared/Error.cshtml";
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var result = new ViewResult
{
ViewName = viewName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
};
result.ViewBag.StatusCode = statusCode;
return result;
}
}
Going through the source code for HandleErrorAttribute.cs, it looks like you can get the status code using the below code
var statusCode = new HttpException(null, filterContext.Exception).GetHttpCode();
I'm trying to learn webapi and have stumbled across a problem. The training course I was doing showed how to do paging by returning a response header with the next and previous link. However it uses HttpContext.Current.Response.Headers.Add() to send back the next link, previous link, and total pages.
I am also trying to implement unit tests for the controllers. Problem seems to be that the HttpContext.Current is null when running through a unit test. I read somewhere that I shouldn't be HttpContext.Current for webapi as it's not testable, but I'm not sure what I should be using instead.
Here is my contoller code:
public partial class CandidateManagerController
{
private readonly ICandidateManager _candidateManagerV2;
public CandidateManagerController(ICandidateManager candidateManager)
{
_candidateManagerV2 = candidateManager;
}
[VersionedRoute("CandidateManager", 2, Name="CandidateManagerV2")]
public IHttpActionResult Get(int page = 1, int pageSize = 1)
{
try
{
var totalCount = 0;
var totalPages = 0;
var result = _candidateManagerV2.GetCandidates(out totalCount, out totalPages, page, pageSize);
var urlHelper = new UrlHelper(Request);
var prevLink = page > 1
? urlHelper.Link("CandidateManagerV2",
new
{
page = page - 1,
pageSize = pageSize,
})
: "";
var nextLink = page < totalPages ? urlHelper.Link("CandidateManagerV2",
new
{
page = page + 1,
pageSize = pageSize
}) : "";
var paginationHeader = new
{
currentPage = page,
pageSize = pageSize,
totalCount = totalCount,
totalPages = totalPages,
previousPageLink = prevLink,
nextPageLink = nextLink
};
HttpContext.Current.Response.Headers.Add("X-Pagination", Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));
return Ok(result);
}
catch (Exception exp)
{
return InternalServerError();
}
}
}
Here is my unit test. Please note I'm using Nunit and Moq:
[TestFixture]
public class CandidateManagerControllerV2Tests
{
[Test]
[Category("CandidateManagerController Unit Tests")]
public void Should_Return_List_Of_Candidate_Objects()
{
var testList = new List<Candidate>();
testList.Add(new Candidate() { CandidateId = 1, Name = "Mr", Surname = "Flibble" });
testList.Add(new Candidate() { CandidateId = 2, Name = "Arnold", Surname = "Rimmer" });
var totalCount = 0;
var totalPages = 0;
var mockManager = new Mock<ICandidateManager>();
mockManager.Setup(x => x.GetCandidates(out totalCount, out totalPages, It.IsAny<int>(), It.IsAny<int>())).Returns(testList);
var controller = new CandidateManagerController(mockManager.Object);
SetupControllerForTests(controller);
var result = controller.Get(1, 1);
}
private static void SetupControllerForTests(ApiController controller)
{
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/candidatemanager");
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
controller.ActionContext=new HttpActionContext();
}
}
I'm hoping someone will be able to help me. It could be that I've been led down a wrong path with the way to implement paging. However it is likely that I'd need to add a response header for something any way.
You should avoid coupling yourself to HttpContext.
Here is another approach to how you can set the header and still be able to unit test it as you intended. You create a HttpResponseMessage, add headers as needed and then create a ResponseMessageResult from it:
//...code removed for brevity
var response = Request.CreateResponse(HttpStatusCode.OK, result);
response.Headers.Add("X-Pagination", Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));
IHttpActionResult ok = ResponseMessage(response);
return ok;
You should also note that your controller setup will cause a null reference error when creating your UrlHelper because you are resetting the controller's Request to null when you assign the default ActionContext
private static void SetupControllerForTests(ApiController controller) {
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/candidatemanager");
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
//commented this out as it was causing Request to be null
//controller.ActionContext=new HttpActionContext();
}
The following test passed when checking for the X-Pagination header
public async Task Should_Return_Paged_List_Of_Candidate_Objects() {
//Arrange
var testList = new List<Candidate>();
testList.Add(new Candidate() { CandidateId = 1, Name = "Mr", Surname = "Flibble" });
testList.Add(new Candidate() { CandidateId = 2, Name = "Arnold", Surname = "Rimmer" });
var totalCount = 0;
var totalPages = 0;
var mockManager = new Mock<ICandidateManager>();
mockManager.Setup(x => x.GetCandidates(out totalCount, out totalPages, It.IsAny<int>(), It.IsAny<int>())).Returns(testList);
var controller = new CandidateManagerController(mockManager.Object);
SetupControllerForTests(controller);
//Act
var response = await controller.Get(1, 1).ExecuteAsync(System.Threading.CancellationToken.None);
//Assert
Assert.IsNotNull(response);
Assert.IsInstanceOfType(response, typeof(HttpResponseMessage));
Assert.IsTrue(response.Headers.Contains("X-Pagination"));
}
To test your response headers you need to do the following:
Initialize your controller with a ControllerContext in TestInitialize
Call the controller where you add the custom header
Assert it in the TestMethod
It only works if you add your header in the controller class: HttpContext.Response.Headers.Add("x-custom-header", "value");
Example:
public class MyControllerTests
{
private MyController _controller;
[TestInitialize]
public void Setup()
{
_controller= new MyController();
_controller.ControllerContext = new ControllerContext()
{
HttpContext = new DefaultHttpContext(),
};
}
[TestMethod]
public async Task GetAsyncShouldContainCutomHeader()
{
// Act
await _controller.GetAsync().ConfigureAwait(false);
// Assert
Assert.IsTrue(_controller.Response.Headers.ContainsKey("x-custom-header"));
Assert.IsTrue(_controller.Response.Headers["x-custom-header"].Equals("value"));
}
}
I have the following Action in my layouts Controller
public JsonResult getlayouts(int lid)
{
List<layouts> L = new List<layouts>();
L = db.LAYOUTS.Where(d => d.seating_plane_id == lid).ToList()
return new JsonResult { Data = L, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
I am calling this Action from another controller like so:
layoutsController L = new layoutsController();
JsonResult result = L.getlayouts(lid);
My question is: how can I get the data from result object?
Well, have a look how you're building the object:
new JsonResult { Data = L, JsonRequestBehavior = JsonRequestBehavior.AllowGet }
You're setting the L variable to a property called Data. So just read that property:
List<layouts> L = (List<layouts>)result.Data;
There's nothing special about the fact that it's an MVC controller action.
You're simply calling a method which returns an object that was constructed in the method, and reading properties from that object. Just like any other C# code.
I have my class:
public class ResponseJson
{
public string message { get; set; }
public bool success { get; set; }
}
in my method SendEmail
private async Task<JsonResult> SendEmailAsync(ApplicationUser user, string returnUrl, string empleadoNombre, string pwdInic)
i will return my JsonResult
ResponseJson response = new ResponseJson();
response.success = true;
response.message = "OperaciĆ³n exitosa";
return new JsonResult( response);
to read the result returned from my SendEmail method
JsonResult emailSend = await SendEmailAsync(user, returnUrl, empleadoNombre, pwdInic);
ResponseJson response = new ResponseJson();
try
{
string json = JsonConvert.SerializeObject(emailSend.Value);
response = JsonConvert.DeserializeObject<ResponseJson>(json);
}
catch(Exception e)
{
}
POST EDITED BELOW
We can't figure out why UrlHelper is returning null strings when used from the WebApi controller's context.
We've done the neccesary debugging but we can't find out why this happens, the RouteData has the routes in it but it doesn't seem to be working.
For the most part, we use a RenderViewToString function, which loads views that consist of calls to Url.RouteUrl(routeName).
Something that's been tried is creating a custom UrlHelper (but to no avail) and debugging with either UrlHelper's (MVC / HTTP).
Attribute routing is used everywhere with route names.
Example usage code:
public class WebApiController : BaseApiController
{
[HttpPost]
[ResponseType(typeof(string))]
[Route("cart/get/checkout", Name = "api.cart.get.checkout")]
public IHttpActionResult GetCheckOutShoppingCart([FromBody] string data)
{
return Ok(RenderViewToString("CartController", "_CheckOutCartPartial", new ShoppingCartModel(Auth.IsAuthenticated ? Auth.GetCustomer().DefaultShippingInfo.CountryId : 148)
{
AddInsurance = false,
InsuredShipping = insuredShipping,
CurrentDeliveryMethodId = deliveryMethodId,
CurrentPaymentMethodId = paymentMethodId
}));
}
}
BaseApiController class:
public class BaseApiController : ApiController
{
public static string RenderViewToString(string controllerName, string viewName)
{
return RenderViewToString(controllerName, viewName, new Dictionary<string, object>());
}
public static string RenderViewToString(string controllerName, string viewName, object model)
{
using (var writer = new StringWriter())
{
var routeData = new RouteData();
routeData.Values.Add("controller", controllerName);
var fakeControllerContext =
new ControllerContext(
new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null),
new HttpResponse(null))), routeData, new FakeController());
var razorViewEngine = new RazorViewEngine();
var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);
var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View,
new ViewDataDictionary(model), new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
public static string RenderViewToString(string controllerName, string viewName, Dictionary<string, Object> data)
{
using (var writer = new StringWriter())
{
var viewData = new ViewDataDictionary();
foreach (var kv in data)
{
viewData[kv.Key] = kv.Value;
}
var routeData = new RouteData();
routeData.Values.Add("controller", controllerName);
var fakeControllerContext =
new ControllerContext(
new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null),
new HttpResponse(null))), routeData, new FakeController());
var razorViewEngine = new RazorViewEngine();
var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);
var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, viewData,
new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
private class FakeController : ControllerBase
{
protected override void ExecuteCore()
{
}
}
}
EDIT
We've put together a class that should in theory work, but it doesn't.
The RouteData has both the MVC and API routes in the collection.
public static class Url
{
public static bool IsWebApiRequest()
{
return
HttpContext.Current.Request.RequestContext.HttpContext.CurrentHandler is
System.Web.Http.WebHost.HttpControllerHandler;
}
public static string RouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
if (IsWebApiRequest())
{
var helper = new System.Web.Http.Routing.UrlHelper();
url = helper.Link(routeName, routeValues);
}
else
{
var helper = new System.Web.Mvc.UrlHelper();
url = helper.RouteUrl(routeName, routeValues);
}
return url;
}
catch
{
return url;
}
}
public static string HttpRouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
if (IsWebApiRequest())
{
var helper = new System.Web.Http.Routing.UrlHelper();
url = helper.Link(routeName, routeValues);
}
else
{
var helper = new System.Web.Mvc.UrlHelper();
url = helper.HttpRouteUrl(routeName, routeValues);
}
return url;
}
catch
{
return url;
}
}
}
This issue has been resolved by building a custom UrlHelper class, which looks into the RouteTable and then returns the url with the patterns replaced.
public static class Link
{
public static string RouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
var route = (Route)RouteTable.Routes[routeName];
if (route == null)
return url;
url = "~/".AbsoluteUrl() + route.Url;
url = url.Replace("{culture}", System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName.ToLower());
if (routeValues == null)
return url;
var values = routeValues.GetType().GetProperties();
Array.ForEach(values, pi => url = Regex.Replace(url, "{" + pi.Name + "}", pi.GetValue(routeValues, null).ToString()));
return url;
}
catch
{
var newUrl = RouteUrl("403");
if(newUrl == String.Empty)
throw;
return newUrl;
}
}
public static string HttpRouteUrl(string routeName, object routeValues = null)
{
return RouteUrl(routeName, routeValues);
}
}
Try Uri.Link(routeName, object)
var uri = Url.Link("api.cart.get.checkout", new { id = 1 });
This will inject the given object's properties into the route if the property name matches a route parameter.
WebApi is not MVC. Even though it looks very similar, it's a completely different system.
UrlHelper (which is MVC) will be looking at the MVC route table, and ignore any WebApi routes.
Try creating the route the hard way, essentially hard-coding the controller and action names into the views. :-(
OK, based on your comment, it seems that you're trying to call your Url.RouteUrl(string routeName, object routeValues = null) or Url.HttpRouteUrl(string routeName, object routeValues = null) from your MVC layout.cshtml file.
If that's the case, the Url.IsWebApiRequest() would return false because the layout.cshtml file is only processed as part of handling MVC request and not WebAPI. That will cause the url generation methods to use MVC route collection instead of the WebAPI collection.
Change your Url class so that RouteUrl always builds WebAPI url and HttpRouteUrl only builds MVC url then in your layout file use proper method based on which kind of url you need in the particular context.
public static class Url
{
public static bool IsWebApiRequest()
{
return
HttpContext.Current.Request.RequestContext.HttpContext.CurrentHandler is
System.Web.Http.WebHost.HttpControllerHandler;
}
public static string RouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
var helper = new System.Web.Http.Routing.UrlHelper();
return helper.Link(routeName, routeValues);
}
catch
{
return url;
}
}
public static string HttpRouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
var helper = new System.Web.Mvc.UrlHelper();
return helper.HttpRouteUrl(routeName, routeValues);
}
catch
{
return url;
}
}
}
I'm unit testing a simple post:
public HttpResponseMessage<Document> PostDocument(Document document)
{
document = repository.Add(document);
var response = new HttpResponseMessage<Document>(document, HttpStatusCode.Created);
var uri = Url.Route(null, new { id = document.Id });
response.Headers.Location = new Uri(Request.RequestUri, uri);
return response;
}
However, the 'URL' and 'Request' are obviously going to be null.
Is there an alternative to mocking out ControllerContext and HttpContext?
Update:
Changed it to:
public HttpResponseMessage<Document> PostDocument(Document document,Uri location = null)
{
document = repository.Add(document);
var response = new HttpResponseMessage<Document>(document, HttpStatusCode.Created);
if (location == null)
{
var uri = Url.Route(null, new { id = document.Id });
location = new Uri(Request.RequestUri, uri);
}
response.Headers.Location = location;
return response;
}
Update 2:
This is better:
public HttpResponseMessage<Document> PostDocument(Document document)
{
var uri = Url.Route(null, new { id = document.Id });
var location = new Uri(Request.RequestUri, uri);
return PostDocument(document, location);
}
[NonAction]
public HttpResponseMessage<Document> PostDocument(Document document, Uri location)
{
document = repository.Add(document);
var response = new HttpResponseMessage<Document>(document, HttpStatusCode.Created);
response.Headers.Location = location;
return response;
}
The Request property should be settable, so you only have to set the ControllerContext (which should have a no-arg constructor so you shouldn't even have to mock).
Using FakeItEasy I got it to work doing this in the TestInitialize.
this.Controller.ControllerContext = new System.Web.Http.Controllers.HttpControllerContext();
this.Controller.Request = A.Fake<HttpRequestMessage>();
Your method might recieve HttpRequestMessage as parameter.
public HttpResponseMessage<Document> PostDocument(Document document, HttpRequestMessage message)
{
}
You can take RequestUri from it. In your unit tests you can put test double of HttpRequestMessage object.