I've a site in MVC4 using areas. In some area (lets call it Area), I have a controller (Controller) with this actions:
public ActionResult Index()
{
return View();
}
public ActionResult OtherAction()
{
return View("Index");
}
This works great if I make a simple redirect to Area/Controller/OtherAction like this:
return RedirectToAction("OtherAction", "Controller", new { area = "Area" });
But I need (check here why) to make a redirect like this:
RouteData routeData = new RouteData();
routeData.Values.Add("area", "Area");
routeData.Values.Add("controller", "Controller");
routeData.Values.Add("action", "OtherAction");
ControllerController controller = new ControllerController();
controller.Execute(new RequestContext(new HttpContextWrapper(HttpContext.ApplicationInstance.Context), routeData));
And in that case it doesn't work. After the last line, the OtherAction method is executed and then in the last line of this code it throws this exception:
The view 'Index' or its master was not found or no view engine
supports the searched locations. The following locations were
searched:
~/Views/Controller/Index.aspx
~/Views/Controller/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/Controller/Index.cshtml
~/Views/Controller/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml
Why is this happening and how can I fix it?
You get the exception because ASP.NET MVC tries to look up your view in the "root" context and not inside the area view directory because you are not setting up the area correctly in the routeData.
The area key needs to be set in the DataTokens collections and not in the Values
RouteData routeData = new RouteData();
routeData.DataTokens.Add("area", "Area");
routeData.Values.Add("controller", "Controller");
routeData.Values.Add("action", "OtherAction");
//...
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 working on an ASP.Net MVC 3 RC project. I have one area named Drivers. I have a LoadPartial() action in a controller in the Drivers area that returns a PartialView(string, object); When this is returned I get an error on my webpage that says "The partial view 'PublicAttendanceCode' was not found." It searched the following locations:
~/Views/AttendanceEvent/PublicAttendanceCode.aspx
~/Views/AttendanceEvent/PublicAttendanceCode.ascx
~/Views/Shared/PublicAttendanceCode.aspx
~/Views/Shared/PublicAttendanceCode.ascx
~/Views/AttendanceEvent/PublicAttendanceCode.cshtml
~/Views/AttendanceEvent/PublicAttendanceCode.vbhtml
~/Views/Shared/PublicAttendanceCode.cshtml
~/Views/Shared/PublicAttendanceCode.vbhtml
Why is it not searching in the Drivers Area?
I have the following pretty basic routes in Global.asax.cs:
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 = UrlParameter.Optional // Parameter defaults
}
);
}
And in DriversAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Drivers_default",
"Drivers/{controller}/{action}/{id}",
new { action = "RequestLeave", id = UrlParameter.Optional }
);
}
What am I missing that will make it look in the drivers area for the partial?
How are you providing area name to PartialView() method? I think you should be passing it in new { area = "Drivers" } as routeValues parameter.
The way that the MVC view engines know the area that they should look in is based on the route that was used to process the request.
In the case of the controller action that you have, are you certain that the request was processed by the area's route definition, or is it possible that the request was processed by the more general route that you defined in global.asax?
There are only four overloads of the method PartialView and it seems like neither of them accept routeValues as a parameter.
I solved this problem like this:
return PartialView(
VirtualPathUtility.ToAbsolute("~/Areas/MyArea/Views/Shared/MyView.cshtml"));
It works, but looks ugly.
This works too:
return PartialView("~/Areas/Admin/Views/Shared/MyView.cshtml", model);
is there any possibility to create a maproute which would use always one method and it won't be necessary to put it in address?
I mean I've got controller with one method (Index) and it displays items depend on methods argument.
public ActionResult Index(string TabName)
{
var tab = (from t in BlogDB.Tabs
where t.TabName == TabName
select t).SingleOrDefault();
ViewData.Model =(Tab)tab;
return View();
}
and what I want is that I can display items putting address "www.example.com/Tabs/TabName" without "/Index/" between Tabs and TabName. I've tried:
routes.MapRoute(
"Tabs1",
"Tabs/{TabName}",
new { controller = "Tabs", action = "Index", TabName = UrlParameter.Optional }
);
But it doesn't work.
do you still have the default route? and if yes is it defined before this one?
Your problem is that asp.net mvc is trying to find the Tabs controller and the Tabname action.
Put this route before the default {controller}/{action} route
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.
I am trying to build a small ASP.NET MVC 2 application.I have a controller class with the below method in it
public ActionResult Index()
{
TestMvc.Models.PersonalInformation objPerson = new TestMvc.Models.PersonalInformation();
objPerson.FirstName = "Shyju";
objPerson.LastName = "K";
objPerson.EmailId="shyju#company.com";
return View(objPerson);
}
And when the page (View) being called, i can see this data there as my view has these data's displaying. Now i want to know how can i pass a query string in the url and use that id to build the PersonalInformation object.Hoe can i read the querystring value ? Where to read ?
I want the quesrtstring to be like
http://www.sitename/user/4234 where 4234 is the user id
http://www.sitename/user/4234 is not a querystring. The querystring is the part of the URL that comes after the ?, as in http://www.sitename/user?userId=42
However, the default routes that come with the MVC project template should allow you to simply change the signature of your action method to
public ActionResult Index(int id)
and you should get the desired result. You should look into how routing works in MVC if you want full control of your URLs.
Also, note that the index action is usually used for showing a list of all objects, so you probably want the Details action for showing 1 user object.
What you want is to modify your action to accept an id like so:
public ActionResult Index(string id)
{
TestMvc.Models.PersonalInformation objPerson = new TestMvc.Models.PersonalInformation();
if (!string.isNullOrEmpty(id))
{
objPerson = getPerson(id);
}
return View(objPerson);
}
Then add a route to your global.asax:
routes.MapRoute(
"MyRoute", // Route name
"user/{id}", // URL with parameters
new { controller = "mycontroller", action = "index", id = ""} // Parameter defaults
);