Return a partial view from a razor page Handler - asp.net-core

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

Related

Razor pages view renders original form value instead of modified when reshown

Moving from ASP.NET Core MVC to Razor pages, there must be an issue in my understanding of passing data to a Razor view.
Here's a simple view:
#page
#model TestModel
#{
System.Diagnostics.Debug.WriteLine(Model.Name);
}
<form class="form-horizontal" method="get">
Name: <input type="text" class="form-control" asp-for="Name">
<button type="submit" class="btn btn-default">Send...</button>
</form>
And here is the view model class with one event handler:
public class TestModel : PageModel
{
[BindProperty(SupportsGet = true)]
public string Name { get; set; } = "";
public TestModel() {}
public IActionResult OnGet()
{
Name += "N";
return Page();
}
}
Then running the project:
Debug.WriteLine shows "N" as expected
input fields has "N" default value as expected
overwrite "N" in the input field eg. to "A" then press Send button
Debug.WriteLine shows "AN" as expected so view has got the value modified by OnGet()
the input field in the page itself contains the value "A" instead of "AN", the generated HTML contains:
value="A"
View does not render the modified Model.Name value but the original from the form data.
How can it be corrected to make view to render the modified string?
You can try to add ModelState.Clear(); in OnGet handler.When binding data in client side,it will get value from ModelState prior to Model.
public class TestModel : PageModel
{
[BindProperty(SupportsGet = true)]
public string Name { get; set; } = "";
public TestModel() {}
public IActionResult OnGet()
{
Name += "N";
ModelState.Clear();
return Page();
}
}

How to pass a data from a controller to _Layout view by using ComponentView

I'm implementing asp.net core project. I have a method in my controller that should pass a data to a viewcomponent and then I need that data to be displayed in _Layout razor view. Below is what I have tried till now:
public class AccountController : Controller {
public IActionResult Index(string str)
{
_httpContext.HttpContext.Items["Shared"] = str;
Debug.WriteLine("str:" + str);
Debug.WriteLine("HttpContext Index shared:"+_httpContext.HttpContext.Items["Shared"]);
// Use ViewData
ViewData["Shared"] = str;
Debug.WriteLine("ViewData Index shared:" + ViewData["Shared"]);
return View();
}
}
public class MySharedDataViewComponent : ViewComponent
{
private readonly IHttpContextAccessor _httpContext;
public MySharedDataViewComponent(IHttpContextAccessor context)
{
_httpContext = context;
}
public Task<IViewComponentResult> InvokeAsync()
{
Debug.WriteLine("MyShred data:" + _httpContext.HttpContext.Items["Shared"]);
return Task.FromResult<IViewComponentResult>(View(_httpContext.HttpContext.Items["Shared"]));
}
}
In index.cshtml for Account controller:
#model string
<h2>#Model</h2>
In Default.cshtml
#model dynamic
#{
var passedDataFromItems = (Model as string);
var passedDataFromViewData = (ViewData["Shared"] as string);
}
#passedDataFromItems
#passedDataFromViewData
In _Layout I added this:
<div class="col-sm-10 col-8 p-0 m-0 text-left">
#await Component.InvokeAsync("MySharedData")
</div>
And in startup I pasted what you suggested as well.
My problem is in _Layout there isn't any data from ViewComponent to be displayed.
First, you need to remove ( ) in _Layout.cshtml, just use #await ComponentAsync("SharedData"). Because ( ) will render HTMLEncoded string instead of HTML string.
Second, if you want to pass your shared data down from Controller, you don't need to call ViewComponent inside Controller. There are several way to pass, ex: HttpContext.Items or ViewData. You don't want to call render HTML from Controller. In previous .NET MVC, we have #Html.RenderAction() to render ChildControlOnly view in Controller. But this is removed, so there are no reason to use Controller to call ViewComponent. Let .NET Core handle that for you by Naming Convention
Third, you don't want to declare #{ Layout = null } in ViewComponent, it is useless because ViewComponent is as PartialView.
Not sure why you try to render whole HTML page in ViewComponent and put it in <head> tag in _Layout.cshtml.
Updated answer with sample code
In your _Layout.cshtml
<html>
<head>
</head>
<body>
<!-- Use this for calling ViewComponent, Name must be prefix of VC class -->
#await ComponentAsync("SharedData")
<!-- Use this for render Body -->
#RenderBody()
</body>
</html>
Example you have HomeController to render Home Page
public class HomeController : Controller
{
private readonly IHttpContextAccessor _httpContext;
public HomeController (IHttpContextAccessor context)
{
_httpContext = context;
}
/// <summary>
/// Define [Route] for parameterize
/// str? means Nullable Param
/// Ex: localhost/hello -> str = hello
/// </summary>
[Route("{str?}")]
public IActionResult Index(string str)
{
_httpContextAccessor.HttpContext.Items["Shared"] = str ?? "Items Empty Param";
ViewData["Shared"] = str ?? "View Data Empty Param";
return View();
}
}
Next you need to create Index.cshtml for placing #RenderBody()
<div>This is home page</div>
Next you need to create SharedDataViewComponentlocales on ViewComponents folder (Under root project)
public class SharedDataViewComponent : ViewComponent
{
private readonly IHttpContextAccessor _httpContext;
public SharedDataViewComponent(IHttpContextAccessor context)
{
_httpContext = context;
}
public Task<IViewComponentResult> InvokeAsync()
{
return Task.FromResult<IViewComponentResult>(View(_httpContext.HttpContext.Items["Shared"]));
}
}
In your Views\Shared\SharedData\Default.cshtml, write with these markup
#model dynamic
#{
var passedDataFromItems = (Model as string);
var passedDataFromViewData = (ViewData["Shared"] as string);
}
#passedDataFromItems
#passedDataFromViewData
Ensure in your Configure method in Startup.cs should add this line
services.AddHttpContextAccessor();

ASP.NET MVC 4 - ListBoxFor, send selectedValue in ActionLink

I have a list of model. I want to retrieve the listBoxSelectedValue to send it in my actionLink to edit it.
This is my view :
#using (Html.BeginForm())
{
#Html.ListBoxFor(a => a.SelectedApplis, new SelectList(ViewBag.Applis,"ID","Name", Model.SelectedApplis))<br/>
#Html.ActionLink("Add","Create","Application")<br/>
#Html.ActionLink("Edit","Edit","Application", null, new { listAppId = Model.SelectedApplis})<br/>
#Html.ActionLink("Delete","Delete","Application")<br/>
}
I created a class "ListBoxApplication" with the List which will contain the selectedValue of the ListBox.
public class ListBoxApplication
{
public IEnumerable<int> SelectedApplis { get; set; }
public ListBoxApplication()
{
SelectedApplis = new List<int>();
}
}
I have 2 controllers : Application and Home
In HomeController, I created the model ListBoxApplication which contain the List. In my ViewBag.Applis, i have all my ApplicationModel.
public ActionResult Index()
{
ListBoxApplication listeApplis = new ListBoxApplication();
ViewBag.Applis = ApplicationModels.GetListApplications();
return View(listeApplis);
}
In my ApplicationController :
public ActionResult Edit(ListBoxApplication listAppId)
{
// I WANT TO RETRIEVE MY listAppId HERE, but it is always 'null'
return View();
}
So I think my problem is in the actionLink :
#Html.ActionLink("Edit","Edit","Application", null, new { listAppId = Model.SelectedApplis})
Me Edit Method is not is the actual controller (Home/Index). I need to send the selectedValue of my ListBox in my actionLink to (Application/Edit).
The listAppId is always 'null'. It doesn't retrieve the value... Is there a mistake in my actionLink ?
Thanks for advance
I don't believe that action links will trigger a postback to the server. Try this instead:
#Html.ActionLink("Delete","Delete","Application")<br/>
#Html.ActionLink("Add","Create","Application")<br/>
#using (Html.BeginForm("Detail","Application"))
{
#Html.ListBoxFor(a => a.SelectedApplis, new SelectList(ViewBag.Applis)) //not sure what the other params you had here were for, but it should work like this
<br/>
<input type="submit" name="Edit" value = "Edit"/>
#*added in response to comment*#
<input type="submit" name="Delete" value = "Delete"/>
<input type="submit" name="Add" value = "Add"/>
}
If you plan on having all of those buttons post back to the server, you could also use ajax (and javascript) to accomplish this same goal, without needing to write out a form for each individual button. Both ways would work just fine, multiple forms is technically easier though.
public ActionResult Detail(ListBoxApplication listAppId, bool Edit, bool Add, bool Delete)
{
if(//check your bools here){
}
return View();
}

access child action parameter in its view

public class AController : Controller
{
public ActionResult Index()
{
return View();
}
}
and Index view goes like
...
#Html.Action("Index", "BController", new { HasEditPermission = true })
...
BControler goes like
public class BController : Controller
{
public ActionResult Index()
{
return PartialView();
}
}
and this Index Partial view goes like
...
#if (!string.IsNullOrEmpty(Request.Params["HasEditPermission"]) && bool.Parse(Request.Params["HasEditPermission"]))
{
// some html rendering
}
...
here when I do Request.Params["HasEditPermission"] in the partial view, I get null.. but if I include HasEditPermission as a parameter to my Index action of BController then I get the value..
I think, I am not getting it from Request.Params because actual request is to AController Index action which doesn't include this parameter.
But I am wondering how is it binding the value to HasEditPermission parameter of Index action(suppose if I have it there) of BController?
Please could someone tell me how do I access it directly in the view? (I am aware using ViewBag in Index is an option)
You can use ValueProvider.GetValue("HasEditPermission").RawValue to access the value.
Controller:
public class BController : Controller
{
public ActionResult Index()
{
ViewBag.HasEditPermission = Boolean.Parse(
ValueProvider.GetValue("HasEditPermission").RawValue.ToString());
return PartialView();
}
}
View:
...
#if (ViewBag.HasEditPermission)
{
// some html rendering
}
...
Update:
Request.Params gets a combined collection of QueryString, Form, Cookies, and ServerVariables items not RouteValues.
In
#Html.Action("Index", "BController", new { HasEditPermission = true })
HasEditPermission is a RouteValue.
you can also try something like this
ViewContext.RouteData.Values["HasEditPermission"]
in your View and subsequent child action views as well..

Creating dropdownlist from values in database

I have a table called Roles with three fields
Guid RoleId
string RoleName
string Description
In my register.cshtml view I want to have a dropdownlist which shows the list of RoleName from Roles table. I also need to be able to get that value and work with it, like assigning the Role to user, which will in done in controller.
My view currently looks like the one below, i'm using model as AspNetUser but it doesn't have knowledge about Role which is what I want to show in dropdownlist.
#model Sorama.CustomAuthentiaction.Models.AspNetUser
#{
ViewBag.Title = "Register";
Layout = "~/Views/shared/_BootstrapLayout.empty.cshtml";
}
#section Styles{
<link href="#Url.Content("~/Content/bootstrap.css")" rel="stylesheet" type="text/css" />
}
<div class ="form-signin">
#using (Html.BeginForm("Register", "Account"))
{
#Html.ValidationSummary(true)
<h2 class="form-signin-heading"> Register </h2>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.Email, new{#placeholder = "Email"})</div>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.UserName, new{#placeholder = "UserName"})</div>
<div class ="input-block-level">#Html.PasswordFor(model=>model.Password, new{#placeholder ="Password"})</div>
<div class ="input-block-level">#Html.DropDownListFor(//don't know what to do
<button class="btn btn-large btn-primary" type="submit">Register</button>
}
</div>
My controller looks like this
public class AccountController : Controller
{
//private readonly IDbContext dbContext;
//
// GET: /Account/
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginModel model)
{
if(Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
[HttpGet]
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
return View(roles);
}
[HttpPost]
public ActionResult Register(AspNetUser model)
{
return View();
}
public ActionResult Index()
{
return View();
}
}
What do I need to do, to have that dropdownlist?
In your controller you need to pass the string[] (IEnumerable<string>) representing your roles into your view somehow...
There are many ways to achieve this, but in your AccountController you could do the following:
public class AccountController : Controller
{
private IDbContext dbContext;
public AccountController(IDbContext dbContext)
{
// Made up field that defines your GetAllRoles method
this.dbContext = dbContext;
}
public ActionResult Register()
{
// Call the GetAllRoles() and capture the result in a variable called roles
var roles = dbContext.GetAllRoles();
return View(new AspNetUser {
Roles = roles
});
}
}
Note: I do not enforce the list to be in any form in the controller (I do not specify it should be a select list), I may want to display the items as in a different way and I let the view be flexible by passing the values only and allowing the view to decide how to render the values.
In your View you can then use where you want the dropdown list to appear:
#Html.DropDownListFor(model => model.Roles, Model.Roles
.Select(role => new SelectListItem { Text = role, Value = role })
As I mentioned, there are many ways to achieve what you are wanting but almost one thing is certain, that with aspnet mvc you will most likely be using the Html helper DropDownListFor MSDN Documentation here:
http://msdn.microsoft.com/en-us/library/system.web.mvc.html.selectextensions.dropdownlistfor(v=vs.108).aspx
EDIT 1:
Create a model to hold the User and Role informations like so:
public class RegisterViewModel
{
public AspNetUser AspNetUser { get; set; }
public IEnumerable<string> Roles { get; set; }
}
In the controller it could look like so:
public class AccountController : Controller
{
private RoleProvider roleProvider;
public AccountController(RoleProvider roleProvider)
{
this.roleProvider = roleProvider;
}
public ActionResult Register()
{
// Call the GetAllRoles() and capture the result in a variable called roles
// var roles = roleProvider.GetAllRoles();
// Or, as you have specified:
var roles = Roles.GetAllRoles();
return View(new RegisterViewModel {
AspNetUser = GetTheAspNetUser(),
Roles = roles
});
}
}
In the View you need to update the model to use:
#model Sorama.CustomAuthentiaction.Models.RegisterViewModel
If you are unwilling/unable to make such a change you could add the list of Roles to the Viewbag:
ViewBag.RoleList = roleProvider.GetAllRoles();
Or as you alluded to:
ViewBag.RoleList = Roles.GetAllRoles();
Then access in the View like so:
#Html.DropDownListFor(model => model.Roles, ViewBag.RoleList
.Select(role => new SelectListItem { Text = role, Value = role })
In a similar scenario, I've done something like this:
private void BagSelectList()
{
ViewBag.List = new SelectList(
db.SetOfCandidateValues,
"KeyPropertyOfTheSet",
"NameOfThePropertyToAppearInTheDropDownList",
selectedValue);
}
And in the view:
#Html.DropDownListFor(
model => model.ForeignKeyProperty,
(SelectList)ViewBag.List)
(Of course, if you dislike the ViewBag, you can do it using the strongly typed view model.)