Is it possible to intercept an action from becoming a ContentResult? - c#

I am attempting to write a filter that wraps data to follow the JSON API spec and so far I've got it working on all cases where I directly return an ActionResult, such as ComplexTypeJSON. I am trying to get it to work in situations like ComplexType where I do not have to run the Json function constantly.
[JSONAPIFilter]
public IEnumerable<string> ComplexType()
{
return new List<string>() { "hello", "world" };
}
[JSONAPIFilter]
public JsonResult ComplexTypeJSON()
{
return Json(new List<string>() { "hello", "world" });
}
However, by the time public override void OnActionExecuted(ActionExecutedContext filterContext) runs when I navigate to ComplexType, the filterContext.Result is a Content Result, that is just a string where filterContext.Result.Content is simply:
"System.Collections.Generic.List`1[System.String]"
Is there a way I can set something up to make ComplexType become JsonResult rather than ContentResult?
For context, here are the exact files:
TestController.cs
namespace MyProject.Controllers
{
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using MyProject.Filters;
public class TestController : Controller
{
[JSONAPIFilter]
public IEnumerable<string> ComplexType()
{
return new List<string>() { "hello", "world" };
}
[JSONAPIFilter]
public JsonResult ComplexTypeJSON()
{
return Json(new List<string>() { "hello", "world" });
}
// GET: Test
[JSONAPIFilter]
public ActionResult Index()
{
return Json(new { foo = "bar", bizz = "buzz" });
}
[JSONAPIFilter]
public string SimpleType()
{
return "foo";
}
[JSONAPIFilter]
public ActionResult Throw()
{
throw new InvalidOperationException("Some issue");
}
}
}
JSONApiFilter.cs
namespace MyProject.Filters
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using MyProject.Exceptions;
using MyProject.Models.JSONAPI;
public class JSONAPIFilterAttribute : ActionFilterAttribute, IExceptionFilter
{
private static readonly ISet<Type> IgnoredTypes = new HashSet<Type>()
{
typeof(FileResult),
typeof(JavaScriptResult),
typeof(HttpStatusCodeResult),
typeof(EmptyResult),
typeof(RedirectResult),
typeof(ViewResultBase),
typeof(RedirectToRouteResult)
};
private static readonly Type JsonErrorType = typeof(ErrorModel);
private static readonly Type JsonModelType = typeof(ResultModel);
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (IgnoredTypes.Any(x => x.IsInstanceOfType(filterContext.Result)))
{
base.OnActionExecuted(filterContext);
return;
}
var resultModel = ComposeResultModel(filterContext.Result);
var newJsonResult = new JsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = resultModel
};
filterContext.Result = newJsonResult;
base.OnActionExecuted(filterContext);
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
if (modelState == null || modelState.IsValid)
{
base.OnActionExecuting(filterContext);
}
else
{
throw new ModelStateException("Errors in ModelState");
}
}
public virtual void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.Exception == null) return;
// Todo: if modelstate error, do not provide that message
// set status code to 404
var errors = new List<string>();
if (!(filterContext.Exception is ModelStateException))
{
errors.Add(filterContext.Exception.Message);
}
var modelState = filterContext.Controller.ViewData.ModelState;
var modelStateErrors = modelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage).ToList();
if (modelStateErrors.Any()) errors.AddRange(modelStateErrors);
var errorCode = (int)System.Net.HttpStatusCode.InternalServerError;
var errorModel = new ErrorModel()
{
status = errorCode.ToString(),
detail = filterContext.Exception.StackTrace,
errors = errors,
id = Guid.NewGuid(),
title = filterContext.Exception.GetType().ToString()
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode = errorCode;
var newResult = new JsonResult() { Data = errorModel, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
filterContext.Result = newResult;
}
private ResultModel ComposeResultModel(ActionResult actionResult)
{
var newModelData = new ResultModel() { };
var asContentResult = actionResult as ContentResult;
if (asContentResult != null)
{
newModelData.data = asContentResult.Content;
return newModelData;
}
var asJsonResult = actionResult as JsonResult;
if (asJsonResult == null) return newModelData;
var dataType = asJsonResult.Data.GetType();
if (dataType != JsonModelType)
{
newModelData.data = asJsonResult.Data;
}
else
{
newModelData = asJsonResult.Data as ResultModel;
}
return newModelData;
}
}
}

There are two options:
1.use ApiController instead of Controller
The apicontroller will return json result,and the default serializer is Newtonsoft.json(here),so you can use like this below:
//the response type
public class SimpleRes
{
[JsonProperty(PropertyName = "result")]
public string Result;
}
//the controller
public class TestController : ApiController
{
[HttpGet]
[HttpPost]
[JSONAPIFilter]
public SimpleRes TestAction()
{
return new SimpleRes(){Result = "hello world!"};
}
}
2.wrap your response with your own ActionResult if you insist using Controller:
//json container
public class AjaxMessageContainer<T>
{
[JsonProperty(PropertyName = "result")]
public T Result { set; get; }
}
//your own actionresult
public class AjaxResult<T> : ActionResult
{
private readonly T _result;
public AjaxResult(T result)
{
_result = result;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Clear();
context.HttpContext.Response.ContentType = "application/json";
var result = JsonConvert.SerializeObject(new AjaxMessageContainer<T>
{
Result = _result,
});
var bytes =
new UTF8Encoding().GetBytes(result);
context.HttpContext.Response.OutputStream.Write(bytes, 0, bytes.Length);
}
}
//your controller
[JSONAPIFilter]
public AjaxResult<List<String>> TestSimple()
{
return AjaxResult<List<String>>(new List<string>() { "hello", "world" });
}
and if you wanna get response string from filter for log or something:
var result = filterContext.Response.Content.ReadAsStringAsync();

I think this is what you are looking for :
public class JSONAPIFilterAttribute : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuted(ActionExecutedContext context)
{
context.Result = new JsonResult
{
Data = ((ViewResult)context.Result).ViewData.Model
};
}
}
From #roosteronacid : return jsonresult in actionfilter

I have just encountered the same issue and found a slightly different approach.
The basic idea was from NOtherDev.
I would introduce an IActionInvoker.
public class ControllerActionInvokerWithDefaultJsonResult : ControllerActionInvoker
{
public const string JsonContentType = "application/json";
protected override ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
{
if (controllerContext.HttpContext.Request.Path.StartsWith("/api/"))
{
return (actionReturnValue as ActionResult)
?? new JsonResult
{
Data = actionReturnValue,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
return base.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue);
}
}
In this case every request starting with "/api/" will have transformed result to json, however only when the actionReturnValue is not a type inherited from ActionResult already.
IActionInvoker is resolved by DependencyResolver, so you need to define the registration in your favorite ioc container which you have set as DependencyResolver.
myFavoriteContainer.Register<IActionInvoker, ControllerActionInvokerWithDefaultJsonResult>(Lifestyle.Transient);
For JsonResult you could use the built-in or this.
In a case you are using async action methods you should inherit from AsyncControllerActionInvoker instead of ControllerActionInvoker and I assume you will need to add an another registration for IAsyncActionInvoker as well. I am not sure about changes in the async part of the invoker itself.

Related

Lowercase Json result in ASP.NET MVC 5

When my json gets returned from the server, it is in CamelCase, but I need lowercase. I have seen a lot of solutions for ASP.NET Web API and Core, but nothing for ASP.NET MVC 5.
[HttpGet]
public JsonResult Method()
{
var vms = new List<MyViewModel>()
{
new MyViewModel()
{
Name = "John Smith",
}
};
return Json(new { results = vms }, JsonRequestBehavior.AllowGet);
}
I want "Names" to be lowercase.
The best solution I have for this is to override the default Json method to use Newtonsoft.Json and set it to use camelcase by default.
First thing is you need to make a base controller if you don't have one already and make your controllers inherit that.
public class BaseController : Controller {
}
Next you create a JsonResult class that will use Newtonsoft.Json :
public class JsonCamelcaseResult : JsonResult
{
private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new StringEnumConverter() }
};
public override void ExecuteResult(ControllerContext context)
{
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = !String.IsNullOrEmpty(this.ContentType) ? this.ContentType : "application/json";
response.ContentEncoding = this.ContentEncoding ?? response.ContentEncoding;
if (this.Data == null)
return;
response.Write(JsonConvert.SerializeObject(this.Data, _settings));
}
}
Then in your BaseController you override the Json method :
protected new JsonResult Json(object data)
{
return new JsonCamelcaseResult
{
Data = data,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
So in the end in your original action returning JSON you just keep it the same and the properties will be camelcased (propertyName) instead of pascalcased (PropertyName) :
[HttpGet]
public JsonResult Method()
{
var vms = new List<MyViewModel>()
{
new MyViewModel()
{
Name = "John Smith",
}
};
return Json(new { results = vms });
}

Using an action filter to get the results of an object and it's action Name

What i'm looking is for a way to use an action filters that can log info about an method/action that is called from another action. Is this possible?
I'm going to give an example i'm trying to make it work:
I have an action with the name Create from the CampaignsController that gets posted and inside that action i call another "action/method" that i want to get info logged.
Wha't i've found so far:
Action filter only work on classes that extend Controller.
I can't use filters on action/method that get called inside another action.
I can put information inside TempData and after that access it inside the action filter, but i don't know if this is the best solution.
Thanks.
The code i'm using is below:
CampaingsController
namespace GBD_01.Controllers
{
[SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
public class CampaignsController : Controller
{
private GBD.FrontOffice.Controllers.FieldsController controller = new GBD.FrontOffice.Controllers.FieldsController();
[HttpGet]
public ActionResult Create(string returnUrl = "")
{
ViewBag.returnUrl = returnUrl;
return View();
}
[HttpPost]
public ActionResult Create(Campaign model, string returnUrl)
{
if (model.Type == "EMAIL")
{
ModelState.Remove("message");
}
else if(model.Type == "SMS")
{
ModelState.Remove("URL");
}
if (ModelState.IsValid)
{
EgoiClass egoiCls = new EgoiClass();
if (model.Type == "EMAIL")
{
egoiCls.createCampaignEmail(model.CampaignName, model.Subject, model.URL);
}
else if (model.Type == "SMS")
egoiCls.createCampaignSMS(model.CampaignName, model.Subject, model.message, model.URL);
}
return View(model);
}
method of the object that is called:
namespace GBD_01.Controllers
{
public class EgoiController
{
public Dictionary<string, object> createCampaignEmail(string subject, string internal_Name, string url)
{
try
{
string apikey = vars.getKeyValue("EgoiApiKey");
if (!String.IsNullOrEmpty(apikey))
{
GBD.FrontOffice.Controllers.FieldsController fieldCtrl = new GBD.FrontOffice.Controllers.FieldsController();
EgoiApi api = EgoiApiFactory.getApi(Protocol.XmlRpc);
EgoiMap map = new EgoiMap();
EgoiMap mapResult = new EgoiMap();
string senderCode = vars.getKeyValue("EgoiSender");/*ProjectSession.EgoiSenderCode*/;
map.Add("apikey", apikey);
map.Add("from", senderCode);
if (!String.IsNullOrEmpty(subject))
map.Add("subject", subject);
/* OPTIONS FOR LINKS IN NEWSLETTERS*/
if (!string.IsNullOrEmpty(vars.getKeyValue("EgoiLinks")))
{
string[] links = vars.getKeyValue("EgoiLinks").Split(';');
foreach (string s in links)
{
map.Add(s, "1");
}
}
if (!String.IsNullOrEmpty(internal_Name))
map.Add("internal_name", internal_Name);
if (!String.IsNullOrEmpty(url))
map.Add("url", url);
map.Add("listID", vars.getKeyValue("EgoiContactListId"));
mapResult = api.createCampaignEmail(map);
Dictionary<string, object> item = (Dictionary<string, object>)mapResult;
if (!item.ContainsKey("ERROR"))
return true;
else
return false;
}
}
catch (Exception ex) { }
return false;
}
}
}
What i mean about action filter is something like:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class LogActionResult : ActionFilterAttribute //TESTE - os action filter apenas funcion em controladores e acções
{
private static readonly ILog log = LogManager.GetLogger(typeof(LogActionResult).Name);
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ActionExecutedContext context = filterContext;
StringBuilder logMessage = new StringBuilder();
Log("OnActionExecuting", filterContext.RouteData);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
Log("OnResultExecuted", filterContext.RouteData);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
Log("OnResultExecuting ", filterContext.RouteData);
}
private void Log(string methodName, RouteData routeData)
{
var controllerName = routeData.Values["controller"];
var actionName = routeData.Values["action"];
var message = String.Format("{0}- controller:{1} action:{2}", methodName,
controllerName,
actionName);
Debug.WriteLine(message);
}
}
I want to use there is a log package to save information about what happened and information about the api call:
log4net.ILog logger = log4net.LogManager.GetLogger("LoginLog");
logger.Info("Acção: action name | result: '"+mapResult+"' | logged at " + DateTime.Now);

Mvc 5 unit tests - set viewbag value of controller

I have an index page which could have a ViewBag value of last search. I want to setup my controller so I'm able to set this ViewBag value before calling my system under test (ProductManagementController)
Index action
[HttpPost]
public async Task<ActionResult> Index(ProductManagementVm postedVm)
{
// Reset pagination if new search
if (postedVm.BookSearch != ViewBag.lastSearch)
{
postedVm.Page = 1;
}
var httpResponseMessage = await_httpService.GetAsync(_urlConfigurations.GetProductList);
var vm = _productFactory.BuildProductManagementVm(
await Task.Run(() => httpResponseMessage.Content.ReadAsStringAsync()), postedVm);
vm.BookSearch = postedVm.BookSearch;
if (string.IsNullOrEmpty(postedVm.BookSearch))
{
postedVm.BookSearch = string.Empty;
}
ViewBag.lastSearch = postedVm.BookSearch;
return View(vm);
}
Setup class
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Web.Mvc;
using ICEBookshop.MVC.App.Controllers;
using ICEBookshop.MVC.App.Interfaces;
using ICEBookshop.MVC.App.Interfaces.Factories;
using ICEBookshop.MVC.App.Models;
using ICEBookshop.MVC.App.Models.ViewModels;
using Moq;
using SpecsFor;
namespace ICEBookshop.MVC.App.Tests.Controllers.ProductManagement
{
public class BaseGiven : SpecsFor<ProductManagementController>
{
protected Mock<IHttpService> HttpServiceMock = new Mock<IHttpService>();
protected Mock<IProductFactory> ProductFactoryMock = new Mock<IProductFactory>();
protected Mock<IUrlConfigurations> UrlConfigurationsMock = new Mock<IUrlConfigurations>();
protected Mock<IJsonValidator> JsonValidatorMock = new Mock<IJsonValidator>();
protected ProductManagementController ProductManagementController;
protected HttpResponseMessage HttpResponseMessage;
protected string JsonContent;
protected bool IsModelStateValid;
protected ActionResult ActionResult;
protected RedirectToRouteResult RedirectToRouteResult;
protected ViewResult ViewResult;
protected ProductManagementVm ProductManagementVm;
protected ProductViewModel ProductViewModel;
protected void BaseGivenSetup()
{
ProductManagementController = new ProductManagementController(HttpServiceMock.Object,
ProductFactoryMock.Object, UrlConfigurationsMock.Object, JsonValidatorMock.Object);
SUT = ProductManagementController;
}
}
}
I would like to set ProductManagementController.ViewBag.SomeName = "some string" so when I step into the controller I test that scenario but at the moment it's null.
Does anyone know how to set a ViewBag value of a controller before testing it?
Unit test
public class WhenServiceReturnProductsAndViewBagHasSearchString : GivenGoingToIndexActionInProductsManagement
{
protected override void When()
{
HttpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("some string content from the service")
};
HttpServiceMock.Setup(expr => expr.GetAsync(It.IsAny<string>())).ReturnsAsync(HttpResponseMessage);
ProductFactoryMock.Setup(
expr => expr.BuildProductManagementVm(It.IsAny<string>(), It.IsAny<ProductManagementVm>()))
.Returns(new ProductManagementVm());
// This doesn't work :(
SUT.ViewBag.LastSearch = "Hey I'm a search string :D";
BaseGivenSetup();
ActionResult = SUT.Index(new ProductManagementVm()).Result;
ViewResult = (ViewResult)ActionResult;
ProductManagementVm = (ProductManagementVm)ViewResult.Model;
}
[Test]
public void ThenActionResultIsNotNull()
{
Assert.IsNotNull(ActionResult);
}
[Test]
public void ThenAViewResultIsNotNull()
{
Assert.IsNotNull(ViewResult);
}
[Test]
public void ThenProductManagementVmIsNotNull()
{
Assert.IsNotNull(ProductManagementVm);
}
}
ViewBag gets its data from the ViewData property
public dynamic ViewBag
{
get
{
if (_dynamicViewDataDictionary == null)
{
_dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
}
return _dynamicViewDataDictionary;
}
}
So you need to populate the value you want there to have access to it in the ViewBag
Here is a POC
[TestClass]
public class ViewBagTests {
[TestMethod]
public void ViewBag_ShouldBe_PrePopulated() {
//Arrange
var SUT = new TargetController();
var expected = "Hey I'm the old search string :D";
SUT.ViewData["LastSearch"] = expected;
//Act
var actual = SUT.Index() as ViewResult;
//Assert
Assert.AreEqual(expected, actual.Model);
}
class TargetController : Controller {
public ActionResult Index() {
var previous = ViewBag.LastSearch;
return View((object)previous);
}
}
}

Versioning ASP.NET Web API 2 with Media Types

I'm using ASP.NET Web API 2 with attribute routing but i can't seem to get the versioning using media types application/vnd.company[.version].param[+json] to work.
I get the following error:
The given key was not present in the dictionary.
which originates from testing the key _actionParameterNames[descriptor] in FindActionMatchRequiredRouteAndQueryParameters() method.
foreach (var candidate in candidatesFound)
{
HttpActionDescriptor descriptor = candidate.ActionDescriptor;
if (IsSubset(_actionParameterNames[descriptor], candidate.CombinedParameterNames))
{
matches.Add(candidate);
}
}
Source: ApiControllerActionSelector.cs
After further debugging I've realized that if you have two controllers
[RoutePrefix("api/people")]
public class PeopleController : BaseApiController
{
[Route("")]
public HttpResponseMessage GetPeople()
{
}
[Route("identifier/{id}")]
public HttpResponseMessage GetPersonById()
{
}
}
[RoutePrefix("api/people")]
public class PeopleV2Controller : BaseApiController
{
[Route("")]
public HttpResponseMessage GetPeople()
{
}
[Route("identifier/{id}")]
public HttpResponseMessage GetPersonById()
{
}
}
you can't use your custom ApiVersioningSelector : DefaultHttpControllerSelector because it will test the keys,as stated above, from all controllers having the same [RoutePrefix("api/people")] and obviously an exception will be thrown.
Just to be sure the right controller was selected
I don't know if this is a bug, but using route [RoutePrefix("api/v1/people")] to version API makes me sad.
NOTE: This works great without attribute routing.
UPDATE
public class ApiVersioningSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _HttpConfiguration;
public ApiVersioningSelector(HttpConfiguration httpConfiguration)
: base(httpConfiguration)
{
_HttpConfiguration = httpConfiguration;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();
var attributedRoutesData = request.GetRouteData().GetSubRoutes();
var subRouteData = attributedRoutesData.LastOrDefault(); //LastOrDefault() will get PeopleController, FirstOrDefault will get People{version}Controller which we don't want
var actions = (ReflectedHttpActionDescriptor[])subRouteData.Route.DataTokens["actions"];
var controllerName = actions[0].ControllerDescriptor.ControllerName;
//For controller name without attribute routing
//var controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor oldControllerDescriptor;
if (controllers.TryGetValue(controllerName, out oldControllerDescriptor))
{
var apiVersion = GetVersionFromMediaType(request);
var newControllerName = String.Concat(controllerName, "V", apiVersion);
HttpControllerDescriptor newControllerDescriptor;
if (controllers.TryGetValue(newControllerName, out newControllerDescriptor))
{
return newControllerDescriptor;
}
return oldControllerDescriptor;
}
return null;
}
private string GetVersionFromMediaType(HttpRequestMessage request)
{
var acceptHeader = request.Headers.Accept;
var regularExpression = new Regex(#"application\/vnd\.mycompany\.([a-z]+)\.v([0-9]+)\+json",
RegexOptions.IgnoreCase);
foreach (var mime in acceptHeader)
{
var match = regularExpression.Match(mime.MediaType);
if (match != null)
{
return match.Groups[2].Value;
}
}
return "1";
}
}
Thanks for the sharing your code. I have modified your version controller selector like below and tried some scenarios and it seems to work well. Can you try updating your controller selector like below and see if it works?
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor controllerDescriptor = null;
// get list of all controllers provided by the default selector
IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//check if this route is actually an attribute route
IEnumerable<IHttpRouteData> attributeSubRoutes = routeData.GetSubRoutes();
var apiVersion = GetVersionFromMediaType(request);
if (attributeSubRoutes == null)
{
string controllerName = GetRouteVariable<string>(routeData, "controller");
if (controllerName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
string newControllerName = String.Concat(controllerName, "V", apiVersion);
if (controllers.TryGetValue(newControllerName, out controllerDescriptor))
{
return controllerDescriptor;
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
else
{
// we want to find all controller descriptors whose controller type names end with
// the following suffix(ex: CustomersV1)
string newControllerNameSuffix = String.Concat("V", apiVersion);
IEnumerable<IHttpRouteData> filteredSubRoutes = attributeSubRoutes.Where(attrRouteData =>
{
HttpControllerDescriptor currentDescriptor = GetControllerDescriptor(attrRouteData);
bool match = currentDescriptor.ControllerName.EndsWith(newControllerNameSuffix);
if (match && (controllerDescriptor == null))
{
controllerDescriptor = currentDescriptor;
}
return match;
});
routeData.Values["MS_SubRoutes"] = filteredSubRoutes.ToArray();
}
return controllerDescriptor;
}
private HttpControllerDescriptor GetControllerDescriptor(IHttpRouteData routeData)
{
return ((HttpActionDescriptor[])routeData.Route.DataTokens["actions"]).First().ControllerDescriptor;
}
// Get a value from the route data, if present.
private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
{
object result = null;
if (routeData.Values.TryGetValue(name, out result))
{
return (T)result;
}
return default(T);
}

ASP.NET MVC - HybridViewResult (ViewResult /PartialViewResult)

Is it possible to build a hybrid ViewResult that returns in depedency of an AjaxRequest or HttpRequest a PartialViewResult or ViewResult?
IsAjaxRequest --> return PartialViewResult
!IsAjaxRequest --> return ViewResult
As far as I know my HybridViewResult should derive from ViewResultBase.
But how to implement the FindView method?
Try:
public class HybridViewResult : ActionResult
{
public string ViewName { get; set; }
public HybridViewResult () { }
public HybridViewResult (string viewName ) { this.ViewName = viewName ; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null) throw new ArgumentNullException("context");
var usePartial = ShouldUsePartial();
ActionResult res = GetInnerViewResult(usePartial);
res.ExecuteResult(context);
}
private ActionResult GetInnerViewResult(bool usePartial)
{
var view = ViewName;
ActionResult res;
if(String.IsNullOrEmpty(view)) {
res = usePartial ? new PartialViewResult(view) : new ViewResult(view);
}
else {
res = usePartial ? new PartialViewResult() : new ViewResult();
}
return res;
}
private bool ShouldUsePartial(ControllerContext context) {
return false; //your code that checks if you need to use partial here
}
}
Add any constructor & GetInnerViewResult variations as needed i.e. to pass Model.
This is a slightly more stripped down take on eglasius's answer. I'm actually tackling a similar problem except I need to return a JsonResult.
The (untested) NormalOrAjaxResult simply lets you specify an action result for the non ajax request and one for the ajax request. Because these are ActionResults you can mix up Redirect, View, Partial and Json view results.
public class NormalOrAjaxResult : ActionResult
{
private readonly ActionResult _nonAjaxActionResult;
private readonly ActionResult _ajaxActionResult;
public NormalOrAjaxResult(ActionResult nonAjaxActionResult, ActionResult ajaxActionResult)
{
_nonAjaxActionResult = nonAjaxActionResult;
_ajaxActionResult = ajaxActionResult;
}
public override void ExecuteResult(ControllerContext context)
{
var isAjaxRequest = context.HttpContext.Request["isAjax"];
if (isAjaxRequest != null && isAjaxRequest.ToLower() == "true")
{
_ajaxActionResult.ExecuteResult(context);
} else
{
_nonAjaxActionResult.ExecuteResult(context);
}
}
}
can you not just make 2 different actions in that case? the 'shared' logic you could simply put in a [nonAction] method?
I know I'm really late to the party here, but these didnt seem quite right to me, so here's my 2 cents:
public class PartialViewConverter : ViewResult
{
public ViewResultBase Res { get; set; }
public PartialViewConverter(ViewResultBase res) { Res = res; }
public override void ExecuteResult(ControllerContext context)
{
Res.ExecuteResult(context);
}
public static ViewResult Convert(ViewResultBase res)
{
return new PartialViewConverter(res);
}
}
With usage:
return PartialViewConverter.Convert(PartialView());
And then in your controller if you override View
protected override ViewResult View(string viewName, string masterName, object model)
{
//Whichever condition you like can go here
if (Request.QueryString["partial"] != null)
return PartialViewConverter.Convert(PartialView(viewName, model));
else
return base.View(viewName, masterName, model);
}
Any action method where you return a view will automatically also return partials when requested:
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
//This will return a partial if partial=true is passed in the querystring.
return View();
}

Categories