I have an Index action on a controller that's not doing anything.
public EmptyModel Index()
{
return null;
}
The Index view simply displays some html, with jQuery-driven ajax and the MasterPage doing all the heavy lifting on this particular page. When I remove this action function from it's controller, the aspx view will no longer display.
More Information and Update:
After making the changes mentioned in Chad's answer the url that used to return the index view now instead returns a 404. This issue may exist because most of the views' folder structure is done in the early Fubu Framework style (with View_Page_Type_Declarations.cs and no code-behinds), rather than using the more intuitive and more recent default folder conventions. But it's possible my analysis is off.
Here's my FubuRegistry:
public WebAppFubuRegistry()
{
IncludeDiagnostics(true);
Services(x => x.SetServiceIfNone<IWebAppSecurityContext, WebAppSecurityContext>());
Applies.ToThisAssembly()
.ToAssemblyContainingType<HomeController>();
Actions
.IncludeClassesSuffixedWithController();
Routes
.UrlPolicy<WebAppUrlPolicy>()
.IgnoreControllerNamespaceEntirely()
.ConstrainToHttpMethod(action => action.Method.Name.StartsWith("Perform"), "POST");
Views
.TryToAttach(x=> x.by<ViewAndActionInDifferentFolders>())
.TryToAttachWithDefaultConventions()
.RegisterActionLessViews(WebFormViewFacility.IsWebFormView,
chain => chain.PartialOnly());
/*Behavior Code */
}
WebAppUrlPolicy:
public class WebAppUrlPolicy : IUrlPolicy
{
public bool Matches(ActionCall call, IConfigurationObserver log)
{
return true;
}
public IRouteDefinition Build(ActionCall call)
{
if(call.IsForHomeController())
return new RouteDefinition("home");
if(call.IsAnIndexCall())
return new RouteDefinition(call.ControllerPrefix());
var otherRoute = new RouteDefinition(call.ToControllerActionRoute());
return otherRoute;
}
}
ViewAndActionInDifferentFolders:
public class ViewAndActionInDifferentFolders : IViewsForActionFilter
{
public IEnumerable<IViewToken> Apply(ActionCall call, ViewBag views)
{
if (call.IsForHomeController())
{
var viewTokens = views.ViewsFor(call.OutputType()).Where(x => x.Name == "HomeIndexView");
return new[] { new WebAppViewToken(call, viewTokens, "home") };
}
if (call.IsJsonCall())
{
return new List<IViewToken>();
}
return CreateSingleTokenList(call, views);
}
private static IEnumerable<WebAppViewToken> CreateSingleTokenList(ActionCall call, ViewBag views)
{
return new[] { new WebAppViewToken(call, views.ViewsFor(call.OutputType())) };
}
}
How do I reconfigure Fubu so that I can use a view without the action?
What changes need to be made to remove the action function above, and still maintain the same functionality?
In your FubuRegistry, in the "Views" section, add:
.RegisterActionLessViews(WebFormViewFacility.IsWebFormView, chain => chain.PartialOnly());
For example, the whole views section may look like:
Views
.TryToAttachWithDefaultConventions()
.RegisterActionLessViews(
WebFormViewFacility.IsWebFormView,
chain => chain.PartialOnly());
Note that you can both ASPX and ASCX for headless views. If you only want ASCX files, then you can use WebFormViewFacility.IsWebFormControl instead.
Works for me:
Views.RegisterActionLessViews(type => type.Name == "StaticView",
chain => chain.Route = new RouteDefinition("StaticView"));
Related
I think about migrating from .net core mvc to razor pages so I am building demo application where I try the features from mvc i used and i stucked a little when I am trying to reload some part of the page based on ajax request using partial view. Sometimes partial view is very simple, like in the following example and sometimes very complex (it can contains additional nested partial views with forms etc. and suprisingly its working well).
My CustomersModel : PageModel handler looks like
it has return JsonResult because i need feedback about errors
sometimes I return more than one partial view
public JsonResult OnGetCustomerDetailPartialView(int id)
{
PopulateCustomers();
var model = new PartialViews.CustomerDetailViewModel()
{
Customer = Customers.Where(x => x.Id == id).FirstOrDefault()
};
var partialView = PartialViewHelper.PartialView("/PartialViews/CustomerDetailViewModel.cs", model, ViewData, TempData);
return new JsonResult(new { success = true, html = partialView.ToStringExtension() });
}
Partial View helper
public static class PartialViewHelper
{
public static PartialViewResult PartialView(string viewName, object model,
ViewDataDictionary viewData, ITempDataDictionary tempData)
{
viewData.Model = model; <-- this line throws error
return new PartialViewResult()
{
ViewName = viewName,
ViewData = viewData,
TempData = tempData
};
}
}
and the problem here is that i got an error
System.InvalidOperationException: 'The model item passed into the
ViewDataDictionary is of type
'RazorPages.PartialViews.CustomerDetailViewModel', but this
ViewDataDictionary instance requires a model item of type
'RazorPages.Pages.CustomersModel'.'
So the ViewData are bind to the CustomerModel, it is possible to return partial view specific ViewModel ?
The bottom line question is, should I approach Razor Pages as an replacement for the MVC or they are intended for less complicated projects than MVC ?
In response to the technical issue, try this version of your method:
public static class PartialViewHelper
{
public static PartialViewResult PartialView<T>(string viewName, object model, ViewDataDictionary viewData, ITempDataDictionary tempData)
{
return new PartialViewResult()
{
ViewName = viewName,
ViewData = new ViewDataDictionary<T>(viewData, model),
TempData = tempData
};
}
}
Then call it as follows (although the name of the partial view doesn't look right to me):
var partialView = PartialViewHelper.PartialView<PartialViews.CustomerDetailViewModel>("/PartialViews/CustomerDetailViewModel.cs", model, ViewData, TempData);
And in reply to the bottom line question, Razor Pages builds on MVC. Anything that you can do with MVC, you can also do with Razor Pages. It is intended to replace MVC for server-side generation of HTML. You can build as complicated an application as you like with it. But your code will likely be a lot simpler than an equivalent MVC application, which is a good thing, right?
I am using the Attribute Routing from MVC5 in my controllers.
Question:
Is there a way to control attribute routing precedence among controllers?
Consider the following
[Route("home/{action=index}/{username?}")]
public class HomeController : Controller
{
[Route("home/index/{username?}", Order = 1)]
[Route("home/{username?}", Order = 2)]
[Route("{username?}", Order = 3)]
public ActionResult Index()
{
// ... bunch of stuff
}
}
Base on the code above, HomeController.Index() action method should be invoked using the following requests:
domain/
domain/{username}
domain/home/
domain/home/{username}
domain/home/index/
domain/home/index/{username}
Second Controller:
[Authorize(Roles = "Member")]
[Route("profile/{action=index}")]
public class ProfileController : Controller
{
[Route("profile")]
public ActionResult Index()
{
}
}
The ProfileController.Index() should be invoked using the following request.
domain/profile
domain/profile/index
The problem
From the examples, if I send domain/profile in the url, an ambiguity exception is thrown. It seems that there is an ambiguity between domain/{username} and domain/profile.
Now, if I used convention-based routing, this would have worked (first match wins). But can it be done in MVC5 Attribute Routing? because I found that a third party library supports precedence among controllers
https://github.com/mccalltd/AttributeRouting/wiki/Controlling-Route-Precedence
routes.MapAttributeRoutes(config =>
{
config.AddRoutesFromController<ProfileController>();
config.AddRoutesFromController<HomeController>();
});
No, it is not possible in ASP.Net MVC 5.2.3 to prioritise controller routes over each other. If multiple match, then the order of the actions is ignored and an exception is thrown.
I have verified this by downloading the source from https://aspnetwebstack.codeplex.com/SourceControl/latest and checking the function GetControllerTypeFromDirectRoute (below). None of the calls made out of this function do anything to prioritise the routes, they are just found and reported back. As you can see, GetControllerTypeFromDirectRoute just throws on a multiple match.
Not great at all, but hopefully this will save someone else some time.
I put a manually mapped route in to avoid this issue.
private static Type GetControllerTypeFromDirectRoute(RouteData routeData)
{
Contract.Assert(routeData != null);
var matchingRouteDatas = routeData.GetDirectRouteMatches();
List<Type> controllerTypes = new List<Type>();
foreach (var directRouteData in matchingRouteDatas)
{
if (directRouteData != null)
{
Type controllerType = directRouteData.GetTargetControllerType();
if (controllerType == null)
{
// We don't expect this to happen, but it could happen if some code messes with the
// route data tokens and removes the key we're looking for.
throw new InvalidOperationException(MvcResources.DirectRoute_MissingControllerType);
}
if (!controllerTypes.Contains(controllerType))
{
controllerTypes.Add(controllerType);
}
}
}
// We only want to handle the case where all matched direct routes refer to the same controller.
// Handling the multiple-controllers case would put attribute routing down a totally different
// path than traditional routing.
if (controllerTypes.Count == 0)
{
return null;
}
else if (controllerTypes.Count == 1)
{
return controllerTypes[0];
}
else
{
throw CreateDirectRouteAmbiguousControllerException(controllerTypes);
}
}
I'm trying to make a way to disable some view helpers that are inside "application/views/helpers"...
What I really want is to put some options on the application.ini to enable or disable some Helpers.
Example on application.ini:
helpers.Helper1=on
helpers.Helper2=off
Now the problem is that when a Helper is off, I want to rewrite some functions of this helper in order to return a different result on the view. In this way, I don't need to change anything in the view script.
I thought in having 2 different php files for each helper, in different locations. One with the real helper and another with the changed helper (to work when it is off on the application.ini).
The problem is that I don't know how to tell the view which one it shoul load...
Does anyone know how it could be done?
FINAL CODE
Ok, after many tries, I put it to work with the following code:
Bootstrap
protected function _initConfigureHelpers(){
$this->bootstrap('view');
$view = $this->getResource('view');
$view->addHelperPath("./../library/ConfigHelpers","Configurable_Helper");
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Application_Plugin_ViewPlugins());
return $view;
}
Application_Plugin_ViewPlugins
class Application_Plugin_ViewPlugins extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request){
$front=Zend_Controller_Front::getInstance();
$bootstrap=$front->getParam('bootstrap');
$options=$bootstrap->getOption("helpers");
if (is_array($options)){
$view = $bootstrap->getResource('view');
foreach($options as $option => $value){
$helper=$view->getHelper($option);
if ($helper){
if ($value=="off")
$helper->__disable();
else if ($value!="on")
throw new Exception('The value of helpers.'.$option.' must be "on" or "off" on application.ini.');
} else {
throw new Exception("Inexistent Helper");
}
}
}
}
}
Modified helper example
require_once APPLICATION_HELPERS."CssCrush.php";
class Configurable_Helper_CssCrush extends Zend_View_Helper_CssCrush {
protected $__config_enabled = true;
public function __disable(){
$this->__config_enabled = false;
return $this;
}
public function __enable(){
$this->__config_enabled = true;
return $this;
}
public function cssCrush(){
if ($this->__config_enabled){
return parent::cssCrush();
} else{
return new Modified_CssCrush();
}
}
}
class Modified_CssCrush {
public static function file ( $file, $options = null ) {
return $file;
}
}
APPLICATION_HELPERS is defined on /public/index.php as "../application/views/helpers/".
Now, when I want to add a configurable helper, I put the original helper on "/application/views/helpers/" and then, create a modified version of it on "/library/ConfigHelpers" with the structure of the example above.
What I think you want is Dependency Injection which is coming in zf2, but not available in zf1.
With some tinkering though you can get what you need.
Configuring helpers in the bootstrap
(assumes default project structure)
View helpers paths config : application/configs/application.ini:
resources.view.helperPath.Zf_View_Helper_ = "Zf/View/Helper"
A simple configurable helper, (allows disable/enable but you can obviously add any methods you need (use this as base class for helpers that need the behaviour)
class Zf_View_Helper_Configurable extends Zend_View_Helper_Abstract
{
protected $isEnabled = true;
public function configurable()
{
return $this;
}
public function disable()
{
$this->isEnabled = false;
return $this;
}
public function enable()
{
$this->isEnabled = true;
return $this;
}
public function __toString()
{
if ($this->isEnabled) {
return 'Configurable is enabled';
} else {
return 'Configurable is disabled';
}
}
}
And configure the helpers in the bootstrap:
public function _initConfigureHelpers()
{
$this->bootstrap('view');
$view = $this->getResource('view');
$configurableHelper = $view->configurable();
$configurableHelper->disable();
}
You can add options in the .ini file and grab them in the bootstrap initConfigureHelpers() method.
If you want this behaviour from any default zf helper, do what #Ratzo said and extend those helpers and add the required behaviour and then configure them in your bootstrap.
Please take a look at the following link Zend_View link
Below is an important points you should consider from the Zend docs.
Note: Default Helper Path
The default helper path always points to the Zend Framework view
helpers, i.e., 'Zend/View/Helper/'. Even if you call setHelperPath()
to overwrite the existing paths, this path will be set to ensure the
default helpers work.
This means that you can't really turn off the helpers, unless you want to go about extending the Zend_View object and overwrite the setHelperPath method. This is not the way to go though.
Here is probably what you want to do. First though, here is my assumption.
Assumption : You want to write your own view helper that slightly alters what the current view helpers do by changing a few methods here or there.
Here is what you should do to accomplish that.
First, write your view helper. Make sure the last part of the class name is the same as the view helper you want to 'overwrite'. You don't have to, but this makes sure the original helper can't be used anymore.
class My_View_Helper_BaseUrl extends Zend_View_Helper_BaseUrl
{
private $_enabled = true;
public function setEnabled( $bool ){ $this->_enabled = (boolean) $bool; }
public function baseUrl(){
if( $this->_enabled ){
return 'testUrl'; //other code
}
else return parent::baseUrl();
}
Now that you have that, do the following
$view->setHelperPath('/path/to/my/helpers', 'My_View_Helper'); //1
echo $view->baseUrl(); //2
Excellent. Now you've effectively shadowed the original BaseUrl helper.
The above code will make it so that the view scans your directory
for any helpers before scanning the default zend directory. When it gets to line
2 the view will find YOUR baseUrl helper first and use THAT instead of the
original baseUrl helper. In the above example it should echo
'testurl' instead of the normal baseUrl behavior.
You can make a custom helper that extends the original helper, for example
class My_Helper_Url extends Zend_View_Helper_Url
{}
and rewrite the methods as you need.
I am trying to load an edit form which is a PartialView, into a div using jQuery. I am overloading the EditUser action. The 1st one is for passing the id and loading the form with existing details. The 2nd one is for posting back the form for save. But it seems to call the 2nd method when I load using jQuery and Url.Action. If I comment out the 2nd EditUser method, then it calls the 1st method. Why is that? How can I make it call the 1st one when I pass staffID? Or is there a better way to implement this Edit form in partial view scenario??
And the CreateUser action works just fine as there is no ambiguity on the overloaded methods as 1 has no parameters and the other has a model as parameter.
Thanks
This is my controller:
public PartialViewResult EditUser(String staffId)
{
User um = userService.GetUserDetails(1, staffId, true);
return PartialView(um);
}
[HttpPost]
public PartialViewResult EditUser(User um)
{
if (!TryUpdateModel(um))
{
ViewBag.updateError = "Edit Failure";
return PartialView("EditUser", um);
}
userService.CreateUpdateUser(um);
return PartialView("ViewUser", um);
}
public PartialViewResult CreateUser()
{
ViewBag.Message = "Create New User";
return PartialView(new User());
}
[HttpPost]
public ActionResult CreateUser(User um)
{
if (!TryUpdateModel(um))
{
ViewBag.updateError = "Create Failure";
return PartialView(um);
}
userService.CreateUpdateUser(um);
return View("Index");
}
This is how I am loading my EditUser partialview:
function menuEdit() {
$('#ActionMenu').hide();
$('#SearchBar').hide();
$('#SearchPanel').hide();
$('#SearchResult').hide();
$('#AddViewEditUser').load("#Url.Action("EditUser","User")", {staffId : sId});
$('#AddViewEditUser').show();
}
According to jQuery .load() "The POST method is used if data is provided as an object; otherwise, GET is assumed." Since you are providing data, .load() is using the method "POST", thus your second EditUser() is being called.
We're building a site that will have very minimal code, it's mostly just going to be a bunch of static pages served up. I know over time that will change and we'll want to swap in more dynamic information, so I've decided to go ahead and build a web application using ASP.NET MVC2 and the Spark view engine. There will be a couple of controllers that will have to do actual work (like in the /products area), but most of it will be static.
I want my designer to be able to build and modify the site without having to ask me to write a new controller or route every time they decide to add or move a page. So if he wants to add a "http://example.com/News" page he can just create a "News" folder under Views and put an index.spark page within it. Then later if he decides he wants a /News/Community page, he can drop a community.spark file within that folder and have it work.
I'm able to have a view without a specific action by making my controllers override HandleUnknownAction, but I still have to create a controller for each of these folders. It seems silly to have to add an empty controller and recompile every time they decide to add an area to the site.
Is there any way to make this easier, so I only have to write a controller and recompile if there's actual logic to be done? Some sort of "master" controller that will handle any requests where there was no specific controller defined?
You will have to write a route mapping for actual controller/actions and make sure the default has index as an action and the id is "catchall" and this will do it!
public class MvcApplication : System.Web.HttpApplication {
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "catchall" } // Parameter defaults
);
}
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new CatchallControllerFactory());
}
}
public class CatchallController : Controller
{
public string PageName { get; set; }
//
// GET: /Catchall/
public ActionResult Index()
{
return View(PageName);
}
}
public class CatchallControllerFactory : IControllerFactory {
#region IControllerFactory Members
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) {
if (requestContext.RouteData.Values["controller"].ToString() == "catchall") {
DefaultControllerFactory factory = new DefaultControllerFactory();
return factory.CreateController(requestContext, controllerName);
}
else {
CatchallController controller = new CatchallController();
controller.PageName = requestContext.RouteData.Values["action"].ToString();
return controller;
}
}
public void ReleaseController(IController controller) {
if (controller is IDisposable)
((IDisposable)controller).Dispose();
}
#endregion
}
This link might be help,
If you create cshtml in View\Public directory, It will appears on Web site with same name. I added also 404 page.
[HandleError]
public class PublicController : Controller
{
protected override void HandleUnknownAction(string actionName)
{
try
{
this.View(actionName).ExecuteResult(this.ControllerContext);
}
catch
{
this.View("404").ExecuteResult(this.ControllerContext);
}
}
}
Couldn't you create a separate controller for all the static pages and redirect everything (other than the actual controllers which do work) to it using MVC Routes, and include the path parameters? Then in that controller you could have logic to display the correct view based on the folder/path parameter sent to it by the routes.
Allthough I don't know the spark view engine handles things, does it have to compile the views? I'm really not sure.
Reflecting on Paul's answer. I'm not using any special view engines, but here is what I do:
1) Create a PublicController.cs.
// GET: /Public/
[AllowAnonymous]
public ActionResult Index(string name = "")
{
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);
// check if view name requested is not found
if (result == null || result.View == null)
{
return new HttpNotFoundResult();
}
// otherwise just return the view
return View(name);
}
2) Then create a Public directory in the Views folder, and put all of your views there that you want to be public. I personally needed this because I never knew if the client wanted to create more pages without having to recompile the code.
3) Then modify RouteConfig.cs to redirect to the Public/Index action.
routes.MapRoute(
name: "Public",
url: "{name}.cshtml", // your name will be the name of the view in the Public folder
defaults: new { controller = "Public", action = "Index" }
);
4) Then just reference it from your views like this:
YourPublicPage <!-- and this will point to Public/YourPublicPage.cshtml because of the routing we set up in step 3 -->
Not sure if this is any better than using a factory pattern, but it seems to me the easiest to implement and to understand.
I think you can create your own controller factory that will always instantiate the same controller class.