ASP.NET MVC - HybridViewResult (ViewResult /PartialViewResult) - c#

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();
}

Related

Can my BaseController have a method return a redirect or true?

That title is misleading, but I'm not sure how to word it better.
My controllers all inherit from BaseController. I would like to have a method in the BaseController that I can call from various actions. I would like something like this:
public virtual object CheckValues(Guid value1, string value2)
{
if (value2 == const_SomeValue || value1 == GetCurrentId())
{
return true;
}
return RedirectToAction("index");
}
Basically, I would like tho have a method that will check certain things and if it fails, does a Redirect. My controller action would check it like this:
public virtual ActionResult overview(Guid? id)
{
CheckValues(id, string.Empty); // on fail this redirects
// Continue with this Action
return View();
}
Many of my controller actions would make use of the CheckValues method.
Is there a good or correct way to do this?
Update: I wanted to share my solution. I liked how it came out.
My controller can now look like this:
[CheckId()] // I can overload the name of the Id, the redirect Action and/or contoller
public virtual ActionResult overview(Guid? id)
{
//... Logic for my action
return View();
}
My filter looks like this:
public class CheckIdAttribute : ActionFilterAttribute
{
public string IdValue { get; set; }
public string RedirectAction { get; set; }
public string RedirectController { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// I wanted to be able to override the redirect and
// the name of the id to check if necessary. Or just
// use defaults.
if (string.IsNullOrEmpty(IdValue))
IdValue = "id";
if (string.IsNullOrEmpty(RedirectAction))
RedirectAction = "index";
if (string.IsNullOrEmpty(RedirectController))
RedirectController = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var isValue1Valid = filterContext.ActionParameters.ContainsKey(IdValue) &&
(filterContext.ActionParameters[IdValue] != null && (Guid)filterContext.ActionParameters[IdValue] != Guid.Empty);
if (!isValue1Valid)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = RedirectAction, controller = RedirectController }));
}
}
}
An alternative to base class methods is action filters. Your controller action could look like this:
[CheckValues(Value1 = "id", Value2 = "")]
public ActionResult overview(Guid? id)
{
// Continue with this Action
return View();
}
Then in the action filter, override OnActionExecuting to check the parameters and possibly redirect.
public class CheckValuesAttribute : ActionFilterAttribute
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var isValue2Valid = filterContext.ActionParameters.ContainsKey(Value2) &&
filterContext.ActionParameters[Value2] == const_SomeValue;
var isValue1Valid = filterContext.ActionParameters.ContainsKey(Value1) &&
filterContext.ActionParameters[Value1] == GetCurrentId();
if (!isValue1Valid || !isValue2Valid)
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = "Index"}));
}
}
The above would still need some tweaking to deal with the case when Value2 is missing/empty string and casting Value1 to a Guid, but that's the gist of it. The line where you set filterContext.Result would short-circuit your action so that it actually never gets executed -- the redirect would happen before the request ever made it to your controller action.

Exception "Security has not been configured for controller" is thrown when ActionNameSelectorAttribute is used

I have a problem with the FluentSecurity when the ActionNameSelectorAttribute is used on controller's action.
public static void Configure()
{
var applicationConfiguration = DependencyResolver.Current.GetService<IApplicationConfiguration>();
var superUserGroupName = applicationConfiguration.GetSuperUserGroupName();
var userGroupName = applicationConfiguration.GetUserGroupName();
var securityConfiguration = SecurityConfigurator.Configure(configuration =>
{
configuration.GetAuthenticationStatusFrom(() => HttpContext.Current.User.Identity.IsAuthenticated);
configuration.GetRolesFrom(System.Web.Security.Roles.GetRolesForUser);
configuration.ForAllControllers().DenyAnonymousAccess().CachePerHttpRequest();
configuration.ForAllControllers().RequireAnyRole(superUserGroupName).CachePerHttpRequest();
configuration.For<Elmah.Mvc.ElmahController>().RequireAnyRole(userGroupName).CachePerHttpRequest();
configuration.ApplyProfile<ProjectSecurityProfile>();
configuration.ApplyProfile<ProjectsSecurityProfile>();
configuration.ApplyProfile<RewecoSecurityProfile>();
configuration.DefaultPolicyViolationHandlerIs(() => new HttpUnauthorizedPolicyViolationHandler());
});
securityConfiguration.AssertAllActionsAreConfigured();
}
When I run the application under the configuration above with the AssertAllActionsAreConfigured everything seems to be correct, no exceptions. But as soon as I call the action methods in the ActualHoursAssignmentController where the HttpParamAction is used , which is the class which inherits from ActionNameSelectorAttribute I get the exception.
Security has not been configured for controller PDATA.Web.Controllers.ActualHoursAssignmentController, action ActionChoiceByNameAttributeValue Area: (not set) Controller: ActualHoursAssignment Action: ActionChoiceByNameAttributeValue
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
public static string ActionChoiceByNameAttributeValue
{
get { return "ActionChoiceByNameAttributeValue"; }
}
public override bool IsValidName([NotNull] ControllerContext controllerContext,
[NotNull] string actionName, [NotNull] MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (actionName == null)
{
throw new ArgumentNullException("actionName");
}
if (methodInfo == null)
{
throw new ArgumentNullException("methodInfo");
}
if (String.IsNullOrWhiteSpace(actionName))
{
throw new ArgumentException("actionName");
}
if (String.IsNullOrWhiteSpace(methodInfo.Name))
{
throw new ArgumentException("methodInfo.Name");
}
if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
return true;
if (!actionName.Equals(ActionChoiceByNameAttributeValue, StringComparison.InvariantCultureIgnoreCase))
return false;
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
}
}
Usage of HttpParamAction attribute in ActualHoursAssignmentController
public class ActualHoursAssignmentController : PdataBaseController
{
[HttpParamAction]
[HttpPost]
public ActionResult UpdateAssignment(ActualHoursAssignmentViewModel vm)
{
}
[HttpParamAction]
[HttpPost]
public ActionResult DeleteAssignment(ActualHoursAssignmentViewModel vm)
{
}
}
UPDATE:
Because I didn't find the solution I temporary eliminate of usage HttpParamActionAttribute. Instead of that I'm using this solution to call multiple buttons in the one Form, but the question persists, maybe it is a bug.
It looks like there is an issue in older versions of FluentSecurity with supporting Controller inheritance, see:
https://github.com/kristofferahl/FluentSecurity/wiki/Securing-controllers#securing-controllers-based-on-inheritance

Posting data when my view model has a constructor does not work

I have the following code:
[HttpGet]
public ActionResult Edit(int req)
{
var viewModel = new EditViewModel();
viewModel.RequestId = int;
return View(viewModel);
}
[HttpPost]
Public ActionResult Edit(EditViewModel viewModel)
{
// some code here...
}
It works fine: when the edit form is posted, I have the action controller who is called.
Now I modify some little bit my code like this:
[HttpGet]
public ActionResult Edit(int req)
{
var viewModel = new EditViewModel(req);
return View(viewModel);
}
[HttpPost]
Public ActionResult Edit(EditViewModel viewModel)
{
// some code here...
}
public class EditViewModel()
{
public EditViewModel(int req)
{
requestId = req;
}
...
}
In this new version, I have a view model with a contructor.
This time, when my form is posted back, the action controller is never triggered.
Any idea?
Thanks.
That's normal. The default model binder can no longer instantiate your view model as it doesn't have a parameterless constructor. You will have to write a custom model binder if you want to use view models that don't have a default constructor.
Normally you don't need such custom constructor. You could simply have your view model like that:
public class EditViewModel()
{
public int RequestId { get; set; }
}
and the POST action like that:
[HttpPost]
public ActionResult Edit(EditViewModel viewModel)
{
// some code here...
}
and now all you have to do is POST the requestId parameter instead of req and the default model binder will do the job.
And if for some reason you wanted to use a view model with custom constructor, here's an example of how the custom model binder might look like:
public class EditViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var req = bindingContext.ValueProvider.GetValue("req");
if (req == null)
{
throw new Exception("missing req parameter");
}
int reqValue;
if (!int.TryParse(req.AttemptedValue, out reqValue))
{
throw new Exception(string.Format("The req parameter contains an invalid value: {0}", req.AttemptedValue));
}
return new EditViewModel(reqValue);
}
}
which will be registered in your Application_Start:
ModelBinders.Binders.Add(typeof(EditViewModel), new EditViewModelBinder());
Asp.Net Core version
public class EditViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var req = bindingContext.ValueProvider.GetValue("req");
if (req == ValueProviderResult.None || string.IsNullOrEmpty(req.FirstValue))
{
bindingContext.ModelState.TryAddModelError("req", "Missing req parameter");
}
int reqValue;
if (!int.TryParse(req.AttemptedValue, out reqValue))
{
bindingContext.ModelState.TryAddModelError($"The req parameter contains an invalid value: {req.AttemptedValue}");
}
var model = new EditViewModel(req.FirstValue);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
You don't need to register anything with startup.cs anymore. Just assign the binder to your ViewModel and you're away.
[ModelBinder(BinderType = typeof(EditViewModelBinder))]
public class EditViewModel

Remove duplication code from methods that depend on return result of other method

In ASP.NET MVC application in controller class I have a method
protected ActionResult VerifySanctions(string sanction)
{
bool hasSanctions = this.qualificationSanctionsService.HasSanctions(sanction);
if (hasSanctions)
{
return this.RedirectToAction("Index", "Home");
}
return null;
}
This method contains common logic from different other actions from different controller
I decided to create a base class for controller that will keep this method and derive other controller from it. But problem is in when I call this method from derived class actions I need to verify if not null and then return the result for example
public ActionResult RegistrationTracking(EncryptedId sourceId)
{
// some code
var registration = learnerRegistrationService.Get(sourceId);
var result = this.VerifySanctions(registration.Qualification);
if (result != null)
{
return result;
}
// some code
return this.View();
}
in other actions I need to verify if result not null again
public ActionResult Index(EncryptedId achievableVersionId)
{
var achievableVersion = achievableVersionService.Get(achievableVersionId);
var verificationResult = this.VerifySanctions(achievableVersion);
if (verificationResult != null)
{
return verificationResult;
}
//some code
return View();
}
And I want to avoid duplication code, can anyone help me with example or advise?
What I need that if don't pass verification by method VerifySanction() then do nothing, else return Action result.
UPDATE the actions could like this
public ActionResult ModifyUnits(EncryptedId sourceId)
{
var registration = learnerRegistrationService.Get(sourceId);
Check.Require(registration != null);
Check.Require(registration.Enrolment != null, "QualificationEnrolment is null - RegistrationAmendments / ModifyUnits");
var result = this.VerifySanctions(registration.Qualification, LookupOrganisationAchievableStatus.Sanction3);
if (result != null)
{
return result;
}
if (!this.organisationAchievableService.IsAccessibleAchievableVersion(registration.Qualification.Id, this.GetOrganisationId()))
{
return new PopupFormResult().Notify(MUI.PleaseSelectACentre);
}
SetEnrolmentViewData(registration.Enrolment);
ViewData["registrationId"] = sourceId;
var isComposite = registration.Enrolment.IsComposite();
ViewData["isComposite"] = isComposite;
this.SetSelectedUnitsViewData(registration, isComposite);
this.SetSelectedQualificationUnitsViewData(isComposite, registration);
return this.PartialView("ModifyUnits", new List<UnitDisplay>());
}
Thanks in advance
This should work:
public ActionResult RegistrationTracking(EncryptedId sourceId)
{
var registration = learnerRegistrationService.Get(sourceId);
return VerifySanctions(registration.Qualification, View());
}
private ActionResult VerifySanctions(Sanction sanction, ViewResult view)
{
bool hasSanctions = this.qualificationSanctionsService.HasSanctions(sanction);
if (hasSanctions)
{
return RedirectToAction("Index", "Home");
}
return view;
}
You could add a method in a default controller extended by all your controller
to embed this logic. For example,
private ActionResult ViewWithSanction(string sanction, ActionResult default)
{
bool hasSanctions = this.qualificationSanctionsService.HasSanctions(sanction);
if (hasSanctions) {
return this.RedirectToAction("Index", "Home");
}
return default;
}
And then your method could looks like
public ActionResult RegistrationTracking(EncryptedId sourceId)
{
var registration = learnerRegistrationService.Get(sourceId);
return ViewWithSanction (registration.Qualification, View());
}
public ActionResult Index(EncryptedId achievableVersionId)
{
var achievableVersion = achievableVersionService.Get(achievableVersionId);
return ViewWithSanction (achievableVersion, View());
}
Pass the Func<ActionResult> that will execute when hasSanctions is false
private ActionResult VerifySanctions(Sanction sanction, Func<ActionResult> whenNoSanctions)
{
bool hasSanctions = this.qualificationSanctionsService.HasSanctions(sanction);
if (hasSanctions)
{
return RedirectToAction("Index", "Home");
}
return whenNoSanctions();
}
And calling
return VerifySanctions(registration.Qualification, ()=> { //any code that returns ActionResult });
UPDATE:
You could extract your not null part as separate method
private ActionResult WhenNoSanctions()
{
if(!this.organisationAchievableService.IsAccessibleAchievableVersion(registration.Qualification.Id, this.GetOrganisationId()))
{
return new PopupFormResult().Notify(MUI.PleaseSelectACentre);
}
SetEnrolmentViewData(registration.Enrolment);
ViewData["registrationId"] = sourceId;
var isComposite = registration.Enrolment.IsComposite();
ViewData["isComposite"] = isComposite;
this.SetSelectedUnitsViewData(registration, isComposite);
this.SetSelectedQualificationUnitsViewData(isComposite, registration);
return this.PartialView("ModifyUnits", new List<UnitDisplay>());
}
And call VerifySanctions as
return VerifySanctions(registration.Qualification, WhenNoSanctions);

ASP.NET MVC 3 - render an optional partial view [duplicate]

Is it possible to determine if a specific view name exists from within a controller before rendering the view?
I have a requirement to dynamically determine the name of the view to render. If a view exists with that name then I need to render that view. If there is no view by the custom name then I need to render a default view.
I'd like to do something similar to the following code within my controller:
public ActionResult Index()
{
var name = SomeMethodToGetViewName();
// The 'ViewExists' method is what I've been unable to find.
if (ViewExists(name))
{
retun View(name);
}
else
{
return View();
}
}
private bool ViewExists(string name)
{
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);
return (result.View != null);
}
For those looking for a copy/paste extension method:
public static class ControllerExtensions
{
public static bool ViewExists(this Controller controller, string name)
{
ViewEngineResult result = ViewEngines.Engines.FindView(controller.ControllerContext, name, null);
return (result.View != null);
}
}
What about trying something like the following assuming you are using only one view engine:
bool viewExists = ViewEngines.Engines[0].FindView(ControllerContext, "ViewName", "MasterName", false) != null;
Here's another [not necessarily recommended] way of doing it
try
{
#Html.Partial("Category/SearchPanel/" + Model.CategoryKey)
}
catch (InvalidOperationException) { }
In asp.net core 2.x and aspnet6 the ViewEngines property no longer exists so we have to use the ICompositeViewEngine service. This a variant of the accepted answer using dependency injection:
public class DemoController : Controller
{
private readonly IViewEngine _viewEngine;
public DemoController(ICompositeViewEngine viewEngine)
{
_viewEngine = viewEngine;
}
private bool ViewExists(string name)
{
ViewEngineResult viewEngineResult = _viewEngine.FindView(ControllerContext, name, true);
return viewEngineResult?.View != null;
}
public ActionResult Index() ...
}
For the curious: The base interface IViewEngine is not registered as a service so we must inject ICompositeViewEngine instead. The FindView() method however is provided by IViewEngine so the member variable may use the base interface.
If you want to re-use this across multiple controller actions, building on the solution given by Dave, you can define a custom view result as follows:
public class CustomViewResult : ViewResult
{
protected override ViewEngineResult FindView(ControllerContext context)
{
string name = SomeMethodToGetViewName();
ViewEngineResult result = ViewEngines.Engines.FindView(context, name, null);
if (result.View != null)
{
return result;
}
return base.FindView(context);
}
...
}
Then in your action simply return an instance of your custom view:
public ActionResult Index()
{
return new CustomViewResult();
}
ViewEngines.Engines.FindView(ViewContext.Controller.ControllerContext, "View Name").View != null
My 2 cents.
Here's how to do it in Razor for Core 2.2 etc. Note that the call is "GetView", not "Find View)
#using Microsoft.AspNetCore.Mvc.ViewEngines
#inject ICompositeViewEngine Engine
...
#if (Engine.GetView(scriptName, scriptName, isMainPage: false).Success)
{
#await Html.PartialAsync(scriptName)
}

Categories