nopCommerce Error: Child actions are not allowed to perform redirect actions - asp.net-mvc

I'm using MVC nopCommerce and developing custom plugin which override existing functionality of HomepageBestSellers (action of ProductController which is attributed as [ChildActionOnly]).
FilterProvider:
namespace Nop.Plugin.Product.BestSellers.Filters
{
public class BestSellersFilterProvider : IFilterProvider
{
private readonly IActionFilter _actionFilter;
public BestSellersFilterProvider(IActionFilter actionFilter)
{
_actionFilter = actionFilter;
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (actionDescriptor.ControllerDescriptor.ControllerType == typeof(ProductController) && actionDescriptor.ActionName.Equals("HomepageBestSellers"))
{
return new Filter[]
{
new Filter(_actionFilter, FilterScope.Action, null)
};
}
return new Filter[] { };
}
}
}
Action Filter:
namespace Nop.Plugin.Product.BestSellers.Filters
{
public class BestSellersFilter : ActionFilterAttribute
{
private readonly ISettingService _settingService;
private readonly IStoreService _storeService;
private readonly IWorkContext _workContext;
public BestSellersFilter(ISettingService settingService,
IStoreService storeService, IWorkContext workContext)
{
this._settingService = settingService;
this._storeService = storeService;
this._workContext = workContext;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//load settings for a chosen store scope and ensure that we have 2 (or more) stores
var storeScope = 0;
if (_storeService.GetAllStores().Count < 2)
storeScope = 0;
var storeId = _workContext.CurrentCustomer.GetAttribute<int>(SystemCustomerAttributeNames.AdminAreaStoreScopeConfiguration);
var store = _storeService.GetStoreById(storeId);
storeScope = store != null ? store.Id : 0;
var bestSellersSettings = _settingService.LoadSetting<BestSellersSettings>(storeScope);
if (bestSellersSettings.IsBestSellersEnabled)
{
filterContext.Result = new RedirectResult("Plugins/BestSellersProducts/PublicInfo");
}
else
base.OnActionExecuting(filterContext);
}
}
}
I am getting the following error on filterContext.Result = new RedirectResult("Plugins/BestSellersProducts/PublicInfo"); this line:
Child actions are not allowed to perform redirect actions.
Description: An unhandled exception occurred during the execution of the current web request.
Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Child actions are not allowed to perform redirect actions.
UPDATE:
Changed BestSellersFilter.cs according to answer.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var storeScope = 0;
if (_storeService.GetAllStores().Count < 2)
storeScope = 0;
var storeId = _workContext.CurrentCustomer.GetAttribute<int>(SystemCustomerAttributeNames.AdminAreaStoreScopeConfiguration);
var store = _storeService.GetStoreById(storeId);
storeScope = store != null ? store.Id : 0;
var featuredProductsSettings = _settingService.LoadSetting<FeaturedProductsSettings>(storeScope);
if (featuredProductsSettings.IsFeaturedProductsEnabled)
{
var products = _productService.GetAllProductsDisplayedOnHomePage();
BestSellersController objResult = new BestSellersController();
filterContext.Result = new ContentResult { Content = objResult.PublicInfoPlugin() };
//base.OnActionExecuting(filterContext);
}
else
base.OnActionExecuting(filterContext);
}
Changed BestSellersController.cs according to answer.
public string PublicInfoPlugin()
{
var featuredProductsSettings = _settingService.LoadSetting<FeaturedProductsSettings>(_storeContext.CurrentStore.Id);
if (featuredProductsSettings.IsFeaturedProductsEnabled)
{
var products = _productService.GetAllProductsDisplayedOnHomePage();
//ACL and store mapping
products = products.Where(p => _aclService.Authorize(p) && _storeMappingService.Authorize(p)).ToList();
//availability dates
products = products.Where(p => p.IsAvailable()).ToList();
if (products.Count == 0)
return "";
var model = PrepareProductOverviewModels(products.Take(featuredProductsSettings.ShowFeaturedProductsNumber)).ToList();
return RenderPartialViewToString("~/Plugins/Product.FeaturedProducts/Views/ProductFeaturedProducts/PublicInfo.cshtml", model);
}
return "";
}
Now getting null values from all the private objects in PublicInfoPlugin method.

Instead of a RedirectResult, set it to a ContentResult and set the Content property to the output from your plugin's action:
filterContext.Result = new ContentResult { Content = "Load your plugin's action here" };

Related

Custom Authorization with Parameters Web API

Can someone show me how to use the parameter in Customize AuthorizeAttribute?
Like this:
[Authorize(Role="Admin,Supervisor")]
[Authorize(User="Me,You")]
[Authorize(Action="abc,def")]
This is my code now and I dont have any idea yet how to add the parameter here.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
ApplicationDbContext _context = new ApplicationDbContext();
public override void OnAuthorization(HttpActionContext actionContext)
{
if (AuthorizeRequest(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
if (((System.Web.HttpContext.Current.User).Identity).IsAuthenticated)
{
actionContext.Response = new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Unauthorized,
Content = new StringContent("You are unauthorized to access this resource")
};
}
else
{
base.HandleUnauthorizedRequest(actionContext);
}
}
private bool AuthorizeRequest(HttpActionContext actionContext)
{
var action = actionContext.ActionDescriptor.ActionName;
var controller = actionContext.ControllerContext.ControllerDescriptor.ControllerName;
var currentUser = actionContext.RequestContext.Principal.Identity.GetUserId();
var user = _context.Users.Join(_context.UserAccesses, x => x.RoleId, y => y.RoleId, (x, y) =>
new { Id = x.Id, firstName = x.firstName, lastName = x.lastName, RoleId = x.RoleId, Controller = y.Controller,
Action = y.Action }).Where(z => z.Id == currentUser && z.Controller == controller && z.Action == action)
.SingleOrDefault();
if (user != null)
return true;
else
return false;
}
}
As you have extended the default implementation of Authorize, you need to use [CustomAuthorize(Role="Admin,Supervisor")]. This will set the roles. You can then access the Roles property directly in your code as they are contained in the parent AuthorizeAttribute which has been inherited.
public override void OnAuthorization(HttpActionContext actionContext)
{
var roles = Roles;
if (AuthorizeRequest(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}

MVC requests for /null returning 404 errors

I have an MVC project that appears to work great, unless you look at the error log. After every page is returned successfully, there is an attempt to load site.example.com/null and I have no idea why. Here's a snippet from Fiddler:
It has no effect to the user, but it is annoying. Here's a sample of the code that's being called:
public class GuidanceController : Controller
{
public ActionResult Index()
{
return View();
}
}
I have nothing unusual in the view and I haven't had to change the RouteConfig. I do have a custom authorization class, like so:
namespace MyProj.Filters {
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class MyProjAuthorizeAttribute : AuthorizeAttribute
{
public AccessLvl[] AccessLvls;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
AuthorizationClient authClient = new AuthorizationClient();
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
return false;
int intId = 0;
string requestId = httpContext.Request.Path.Split('/').Last(); //get ID sent with new page
string referrerId = httpContext.Request.UrlReferrer?.AbsolutePath.Split('/').Last(); //get ID sent with old page
if (int.TryParse(requestId, out intId) != true) //prefer new ID, if available
int.TryParse(referrerId, out intId); //else just use old id
List<AccessLvl> userAccessLevels = authClient.GetAccessLevels((intId == 0) ? null : intId.ToString());
foreach (AccessLvl level in AccessLvls)
{
if (userAccessLevels.Contains(level))
{
return true;
}
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Error",
action = "Unauthorized",
urlReferrer = filterContext.RequestContext.HttpContext.Request.Url
})
);
}
}
}
What am I overlooking? Any ideas are greatly appreciated.

ASP.NET MVC: Unit testing session variables does't change

I'm trying to unit test a Logout action of my controller. My controller recive an interface wich deals with session variables:
public HomeController(IUnitOfWork unitOfWork, ISecurityService security = null)
{
_unitOfWork = unitOfWork;
if (security != null)
{
_securityService = security;
}
else
{
_userService = new UserService(_unitOfWork)
_securityService = new SecurityService(_userService);
}
}
Using Moq, I create a HttpSessionStateBase object
Which I use to create a SecurityService object in my unit test class:
var mockSession = new Mock<HttpSessionStateBase>();
mockSession.SetupGet(s => s["UserID"]).Returns("1");
HttpSessionStateBase session = mockSession.Object;
ISecurityService _securityService = new SecurityService(_userService, session);
My SecurityService
public class SecurityService : ISecurityService
{
private readonly IUsuarioService _userService ;
private readonly HttpSessionStateBase _session;
public int UserID
{
get { return Convert.ToInt32(_session["UserID"]); }
set { _session["UserID"] = value; }
}
public SecurityService(IUserService service, HttpSessionStateBase session = null)
{
_userService = service;
_session = session ?? new HttpSessionStateWrapper(HttpContext.Current.Session);
}
public void Logout()
{
// I'm trying to delete this variable, but I can't
_session["UserID"] = 0;
_session.Clear();
_session.Abandon();
}
}
Finally, my test method
[TestMethod]
public void Should_delete_session_variable_from_the_user()
{
controller.Logout();
// It's allways 1
Assert.IsTrue(Convert.ToInt32(session["UserID"]) == 0);
}
When I debug this code, I can see that the session variable does not chage its value. But, if I open a "Quick Watch" window and execute _session["UserID"] = 0;, its value change.
I can't understand why. This is the first time that I see that a variable does not chage its value in debugging.
Finally I found, the solution is to add an SetupSet method.
This is the final code:
int sessionValue = 0;
var mockSession = new Mock<HttpSessionStateBase>();
mockSession.SetupSet(s => s["UserId"] = It.IsAny<int>())
.Callback((string name, object val) => sessionValue = (int) val);
mockSession.SetupGet(s => s["UserId"]).Returns(() => sessionValue);

HandleErrorAttribute not working

I have started an MVC 3 template project in VS10 and modified global.asax.cs as such:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute { ExceptionType = typeof(DivideByZeroException), View = "DivideByZeroException", Order = 1 });
filters.Add(new HandleErrorAttribute { View = "AllOtherExceptions", Order = 2 });
}
To web.config I added:
<customErrors mode="On">
Then created the corresponding views and finally added a DivideByZero-throw to one of the actions.
The result: The view AllOtherExceptions is rendered.
Much though I hate to disagree with anything Darin says, he is wrong on this one.
There is no problem with setting the properties (that's the way you are supposed to do it).
The only reason your original code didn't work as expected is because you have the Order set wrong.
See MSDN:
The OnActionExecuting(ActionExecutingContext),
OnResultExecuting(ResultExecutingContext), and
OnAuthorization(AuthorizationContext) filters run in forward order.
The OnActionExecuted(ActionExecutedContext),
OnResultExecuting(ResultExecutingContext), and
OnException(ExceptionContext) filters run in reverse order.
So your generic AllOtherExceptions filter needs to be the lowest Order number, not the highest.
Hopefully that helps for next time.
You shouldn't set properties when registering a global action filter. You could write a custom handle error filter:
public class MyHandleErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
{
Exception innerException = filterContext.Exception;
if ((new HttpException(null, innerException).GetHttpCode() == 500))
{
var viewName = "AllOtherExceptions";
if (typeof(DivideByZeroException).IsInstanceOfType(innerException))
{
viewName = "DivideByZeroException";
}
string controllerName = (string)filterContext.RouteData.Values["controller"];
string actionName = (string)filterContext.RouteData.Values["action"];
HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
ViewResult result = new ViewResult
{
ViewName = viewName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
filterContext.Result = result;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
}
}
and then register it:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyHandleErrorAttribute());
}
Do check fear's answer below. It's certainly the simpler, if it works.
Since it came after a few weeks, this is how my filter finally spelled out, using Darins response and incorporating Elmah-reporting, with code from this topic.
I still don't know why you can't set properties on a global action filter.
public class MyHandleErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.IsChildAction &&
(!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
{
var innerException = filterContext.Exception;
if ((new HttpException(null, innerException).GetHttpCode() == 500))
{
var viewName = "GeneralError";
if (typeof (HttpAntiForgeryException).IsInstanceOfType(innerException))
viewName = "SecurityError";
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),
TempData = filterContext.Controller.TempData
};
filterContext.Result = result;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
//From here on down, this is all code for Elmah-reporting.
var version = Assembly.GetExecutingAssembly().GetName().Version;
filterContext.Controller.ViewData["Version"] = version.ToString();
var e = filterContext.Exception;
if (!filterContext.ExceptionHandled // if unhandled, will be logged anyhow
|| RaiseErrorSignal(e) // prefer signaling, if possible
|| IsFiltered(filterContext)) // filtered?
return;
LogException(e);
}
}
}
private static bool RaiseErrorSignal(Exception e)
{
HttpContext context = HttpContext.Current;
if (context == null)
return false;
var signal = ErrorSignal.FromContext(context);
if (signal == null)
return false;
signal.Raise(e, context);
return true;
}
private static bool IsFiltered(ExceptionContext context)
{
var config = context.HttpContext.GetSection("elmah/errorFilter")
as ErrorFilterConfiguration;
if (config == null)
return false;
var testContext = new ErrorFilterModule.AssertionHelperContext(
context.Exception, HttpContext.Current);
return config.Assertion.Test(testContext);
}
private static void LogException(Exception e)
{
HttpContext context = HttpContext.Current;
ErrorLog.GetDefault(context).Log(new Error(e, context));
}
}

How to get parameter in OnActionExecuting?

I modify the default route rule a little bit as below:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id= (string)null } // Parameter defaults
);
Then I can set url as:
/Controller/Action/myParam
/Home/Index/MyParam
The default Action Index would be:
public ActionResult Index(string id)
{
//....
}
I can get the param in action. But I want to get the param in OnActionExecuting. How can I do it?
You should be able to access it with :
public override void OnActionExecuting(ActionExecutingContext filterContext) {
string id = filterContext.RouteData.Values["id"];
//...
}
It can be accessible from ActionArguments inside OnActionExecuting.
public override void OnActionExecuting(ActionExecutingContext context) {
string id = context.ActionArguments["id"].ToString();
//...
}
if you want to get controller, action, and all parameters, you can do this
var valuesStr = new StringBuilder();
if (ctx.RouteData != null && ctx.RouteData.Values != null)
foreach (var v in ctx.RouteData.Values)
valuesStr.AppendFormat("/{0}", v.Value);
_logger.Info("executing {0}", valuesStr.ToString());
which results in the whole path
results with:
"/Get/Customer/215840"
it should work on multiple parameters just as well.
I use the following code to retrieve and compare the parameters passed to an action (.net core 3.1).
var vals = filterContext.ActionArguments.Values;
var fistobj = vals.FirstOrDefault();
var val = fistobj.GetType().GetProperties().FirstOrDefault(x => string.Equals(x.Name, "nameParameter", StringComparison.OrdinalIgnoreCase)).GetValue(fistobj);
if (val == null || val.ToString() != "value parameter")
{
filterContext.Result = new JsonResult(ExecuteResult.Fail(JanException.Parameter.API00001));
//base.OnActionExecuting(filterContext);
return;
}
More details for OnActionExecuting and a custom Attribute InitializingActionAttribute
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ControllerActionDescriptor controlActionDescriptor = (ControllerActionDescriptor)filterContext.ActionDescriptor;
var attributes = controlActionDescriptor.MethodInfo.CustomAttributes;
if (attributes.Any(a => a.AttributeType == typeof(InitializingActionAttribute)))
{
var vals = filterContext.ActionArguments.Values;
var fistobj = vals.FirstOrDefault();
var val = fistobj.GetType().GetProperties().FirstOrDefault(x => string.Equals(x.Name, "nameParameter", StringComparison.OrdinalIgnoreCase)).GetValue(fistobj);
if (val == null || val.ToString() != "value parameter")
{
filterContext.Result = new JsonResult(ExecuteResult.Fail(JanException.Parameter.API00001));
//base.OnActionExecuting(filterContext);
return;
}
}
base.OnActionExecuting(filterContext);
}
From your filterContext you should be able to get whatever you need.
public class MyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Do your stuff here
}
}
[MyAttribute]
public ActionResult Index(string id)
{
//....
}

Resources