I'm looking for a clean way to handle permalinks when my application uses pushstate to load ASP.NET MVC partial views for pagination
If my controller returns a partial view (currently) then the permalink will display the partial view alone without the rest of the site defined by _Layout.cshtml.
If my controller return a regular view, the permalink works but loading the entire page into the container provided only for part of the page creates a type of Droste Effect.
The only solution I've found is to redirect the 404 to the homepage and navigate to correct page using the relevant portion of the url.
However, this prevents me from using my custom error page (which just happens to be an awesome error page).
Ok I figured out how to make this work with a double-controller setup.
NavigationController handles ajax calls and returns partial views (note there is no "Index")
public class NavigationController : Controller
{
public PartialViewResult Home()
{
return PartialView("Home", null);
}
public PartialViewResult About()
{
return PartialView("About", null);
}
...
}
Separate PermalinkController handles permalinks by returning entire regular Views (note this has an "Index" but no "Home":
public class PermalinkController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
...
}
Routes:
routes.MapRoute(
"PermalinkRouter",
"{action}",
new { controller = "Permalink", action = "Index" },
new { }
);
routes.MapRoute(
"NavigationRouter",
"Navigation/{action}/{id}",
new { controller = "Navigation", action = "Home", id = UrlParameter.Optional },
new { }
);
Views:
~/Views/Shared/_Layout.cshtml:
...
<header></header>
<div id="pageViewContainer">
#RenderBody()
</div>
<footer></footer>
...
~/Views/Navigation/Home.cshtml PartialView:
<div> Home Content </div>
~/Views/Permalink/Index.cshtml View:
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#Html.Partial("~/Views/Navigation/Home.cshtml", null)
Related
I have a simple page that also includes a PartialView so that I can refresh the data every 10 seconds returned by the PartialView. It really is a trivial task but I'm not proficient enough with the whole web part and especially JavaScript.
How can I run the LoadData() Method every X seconds so that my partialView shows the data?
I have found the following JS code which I think should be able to refresh the PartialView but I don't know how to adapt this to my situation:
<script type="text/javascript">
$(function () {
setInterval(loadTable, 1000); // invoke load every second
loadTable(); // load on initial page loaded
});
function loadTable() {
$('#data').load('/controller/tabledata');
}
</script>
My index.cshtml looks like this:
#page
#model IndexModel
<div class="data">
<partial name="~/Pages/Shared/_BezoekersData.cshtml" />
</div>
The indexModel:
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.SharePoint.Client;
using PnP.Framework;
using System.Security;
namespace MyNameSpace.Pages
{
public class IndexModel : PageModel
{
public IActionResult OnGetPartial() => Partial("\\Shared\\_BezoekersData.cshtml");
// properties left out for clarity
public void OnGet()
{
LoadData();
}
public void LoadData()
{
// dataRetrieval part here.
}
}
}
_BezoekersData.cshtml:
#{
ViewData["Title"] = "Home page";
}
<div style="position:fixed; left:65px; top:25px; color:#c2a149; font-size:75px; ">#Model.Vestiging</div>
<div style="position:fixed; left:1100px; top:180px; color:#c2a149; font-size:50px">#Model.MaxAantal</div>
<div style="position:fixed; left:1100px; top:285px; color:#c2a149; font-size:50px">#Model.HuidigAantal</div>
You should add one more function to call the partial view and refresh it, check this:
public void OnGet()
{
LoadData();
}
public void LoadData()
{
MyModel=new MyModel {thetime=DateTime.Now };
}
public PartialViewResult OnGetMyPartial() // extra method to get data
{
MyModel = new MyModel { thetime = DateTime.Now };
return Partial("_BezoekersData", MyModel);
}
View:
Mind it must be ** div id="data"**, because you use #data to call jquery function.
Pass model data to partial, add model attribute.
And to call the new method, using handler:
In main view: #Model.MyModel.thetime
<div id="data">
<partial name="~/Pages/Shared/_BezoekersData.cshtml" model="Model.MyModel"/> //add a model attribute
</div>
#section Scripts
{
<script type="text/javascript">
$(function () {
setInterval(loadTable, 1000);
loadTable();
});
function loadTable() {
$('#data').load("/Index?handler=MyPartial"); //handler to call razor method
}
</script>
}
This is the partial view:
#model He621.Models.MyModel
In partial:<div style="position:fixed; left:65px; top:100px; color:#c2a149; font-size:75px; ">#Model.thetime</div>
Result:
The time refreshes every 1 seconds, and will only refresh the partial:
ASP.NET Core's tag helpers appear not to be working in combination with DynamicRouteValueTransformer.
Here's an MCVE: let's say we have a simple controller with two actions...
public class GenericController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Add()
{
return View();
}
}
... and the Index view links to the Add action with the asp-action tag helper:
<!DOCTYPE html>
<html>
<body>
<a asp-action="Add">Add new!</a>
</body>
</html>
When we now open /Generic in a browser and inspect the page, we'll notice that ASP.NET generated the expected href value for the controller action:
Add new!
So far, so good. Now let's create a DynamicRouteValueTransformer which routes all requests to that same controller...
public class Transformer : DynamicRouteValueTransformer
{
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
if (values.ContainsKey("controller"))
{
values["originalController"] = values["controller"];
values["controller"] = "Generic";
}
return new ValueTask<RouteValueDictionary>(values);
}
}
... and let's set it up for endpoint routing in Startup.cs...
services.AddSingleton<Transformer>();
...
app.UseEndpoints(endpoints =>
{
endpoints.MapDynamicControllerRoute<Transformer>("{controller=Home}/{action=Index}/{id?}");
});
After opening /Generic (or any other route, e.g. /Foobar) in a browser and inspecting the page, we'll now notice that asp-action tag helper is no longer working:
Add new!
Since the href value is empty, the link is no longer working. It looks the dynamic routing broke the tag helper.
Any suggested fix or workaround?
I am having a problem returning a partial view from a razor page, my scenario is
I have a partial view which is a form and that has a model. I have 3 forms residing on a single razor pages
Form A post a ModelA
Form B post ModelB
My problem is, i want to handle thier specific post event on the parent Page which is a razor page.
How would i return this partial view
OnPostModelA(ModelA model)
{
if(! ModelState.IsValid)
return Partialview("_CreateModelA", model);
}
Is this possible using razor pages or this is not possible?
I just want to return the partialview with its designated model using ajax.
As you know ,Razor Pages have no equivalent PartialView method on the PageModel. If you do want to invoke different parial views in PageModel method , simply add a PartialView Helper Method in you PageModel:
[NonAction]
public virtual PartialViewResult PartialView(string viewName, object model)
{
ViewData.Model = model;
return new PartialViewResult()
{
ViewName = viewName,
ViewData = ViewData,
TempData = TempData
};
}
Here I use a ViewData.Model to store your model object , let's say your Model type is named as X1Model :
you can use it across the partial views .
Create a simple partial view named as _CreateModelA.cshtml :
#model HelloModel
AAAAA
<div>
#Model.Model.Welcome
</div>
and another partial view named as _CreateModelB.cshtml :
#model HelloModel
BBBBBBBB
<div>
#Model.Model.Welcome
</div>
At last , you can return PartialView in your PageModel:
public class HelloModel : PageModel
{
public X1Model Model { get; set; }
public ActionResult OnGet(int rand = 0)
{
var flag = rand % 2 == 0 ? true : false;
var model = new HelloModel() {
Model = new X1Model {
Welcome = "Hello,world",
}
};
if (flag)
{
return PartialView("_CreateModelA", model);
}
else
{
return PartialView("_CreateModelB", model);
}
}
[NonAction]
public virtual PartialViewResult PartialView(string viewName, object model)
{
// ...
}
}
Here's a screenshot :
However , it is not recommended to put partial view logic in PageModel . Using it in the Page file as below is much nicer:
#if(){
<partial name="" />
}else{
<partial name="" />
}
In asp dotnet core 2.2, Microsoft added a Partial method to the PageModel class that works similar to the PartialView method on the Controller class. It however doesn't allow you to pass ViewData to the view. So, if you need to do that, then you can create your own PartialViewResult like so:
var resultViewData = new ViewDataDictionary<YourModelType>(ViewData, model);
resultViewData[YourViewDataProperty] = yourViewDataValue;
return new PartialViewResult
{
ViewName = "_Branch",
ViewData = resultViewData,
TempData = TempData
};
I'm trying to submit a form from partial view, but keep getting 404.
Here's the form:
#model PMP.WebUI.Models.ViewModel.VMTakeQuiz
#using (Html.BeginForm("Quiz", "QuizController", FormMethod.Post, new { id = "take-quiz-form" }))
{
..
<input type="submit" name="LaunchQuiz" value="Launch Quiz!" class="submit-button" />
..
}
Here's the map route assignment (it's the second assignment after the usual ignore axd's one):
routes.MapRoute(
"Quiz",
"Quiz/Quiz",
new { controller = "Quiz", action = "Quiz" }
);
And here's the controller and its action:
public class QuizController : Controller
{
[HttpPost]
public ActionResult Quiz(VMTakeQuiz quiz)
{
return null;
}
}
What I'm getting is 404 on: Requested URL: /QuizController/Quiz
What should I change to route the form submit to the given action?
Pass just controller name without 'controller' postfix like
#using (Html.BeginForm("Quiz", "Quiz", FormMethod.Post, new { id = "take-quiz-form" }))
{
..
Requested URL should be: /Quiz/Quiz
For controller you don't need to append controller suffix with their names
#using (Html.BeginForm("Quiz", "Quiz", FormMethod.Post, new { id = "take-quiz-form" }))
Also try to have different name for your views and controllers. :)
I might be missing something here, but in ASP.NET MVC 4, I can't get the following to work.
Given the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(string order1, string order2)
{
return null;
}
}
and it's view:
#{
ViewBag.Title = "Home";
}
#using (Html.BeginForm())
{
#Html.TextBox("order1")<br />
#Html.TextBox("order2")
<input type="submit" value="Save"/>
}
When start the app, all I get is this:
The current request for action 'Index' on controller type
'HomeController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Index() on type
ViewData.Controllers.HomeController System.Web.Mvc.ActionResult
Index(System.String, System.String) on type
ViewData.Controllers.HomeController
Now, in ASP.NET MVC 3 the above works fine, I just tried it, so what's changed in ASP.NET MVC 4 to break this?
OK there could be a chance that I'm doing something silly here, and not noticing it.
EDIT:
I notice that in the MVC 4 app, the Global.asax.cs file did not contain this:
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
);
}
which the MVC 3 app does, by default. So I added the above to the MVC 4 app but it fails with the same error. Note that the MVC 3 app does work fine with the above route. I'm passing the "order" data via the Request.Form.
EDIT:
In the file RouteConfig.cs I can see RegisterRoutes is executed, with the following default route:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
I still get the original error, regards ambiguity between which Index() method to call.
Because MVC4 ships with ASP.Net Web.API you can potentially reference two HttpPostAttribute (the same applies to the other attributes like HttpGet, etc.):
System.Web.Mvc.HttpPostAttribute is used by ASP.Net MVC so you need to use it on actions inside Controller derived controllers
System.Web.Http.HttpPostAttribute is used by ASP.Net Web.API so you need to use it on actions inside
ApiController derived controllers
You have acidentally referenced System.Web.Http.HttpPostAttribute in your code. Change it to use the right attribute and it should work correctly:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[System.Web.Mvc.HttpPost]
public ActionResult Index(string order1, string order2)
{
return null;
}
}