Form in Layout: Razor Pages - c#

I have a select in a form in the Layout of my web app that needs to be accessible from every page. The form sets a session variable that is required to load data on every page.
<form asp-page-handler="CustomerChange" method="post" class="navbar-form navbar-left">
<select name="Customer" id="Customer" class="form-control" onchange="this.form.submit()">
<option value="">Select Customer</option>
<option value="Vand">Vandalay</option>
<option value="Kram">Kramerica</option>
</select>
</form>
I know I can make a base PageModel and inherit from that on every page in order to respond to a OnPost e.g.
public abstract class BaseSecurePageModel : PageModel
{
[BindProperty]
public string Customer { get; set; }
public virtual void OnPostCustomerChange()
{
HttpContext.Session.SetString("Customer", Customer);
}
}
but this doesn't lend itself to having the model binded to the form and also requires that I remember to inherit from the base class in every page. Is there a correct way to handle forms that need to be available everywhere?

Try using a Controller instead and have your CustomerChange ActionResult specify a [Route()]. I use a Controller for most of my Layout items such as shopping carts, localization etc... in razor pages and works pretty well.
// Updated answer based on feedback from Steven B.
Below is an example of the localization I spoke about above. The form triggers a post against the SetLanguage method in the BaseController.cs
In the _Layout.cshtml file I have, in this instance, a partial view:
#Html.Partial("_SetLanguagePartial") // Used prior to .net core 2.1
<partial name="_SetLanguagePartial" /> // Used for .net core 2.1+
The html inside this _SetLanguagePartial.cshtml contains a form with the corresponding asp-controller and asp-action
<form id="selectLanguage" asp-controller="Base" asp-action="SetLanguage" asp-route-returnUrl="#returnUrl" method="post" class="form-horizontal" role="form">
<ul class="list-inline">
#foreach (var culture in cultureItems)
{
var countryIcon = "usa.png";
<li>
<button type="submit" class="btn btn-sm btn-link" name="culture" title="#culture.Text" value="#culture.Value">
#switch (culture.Text)
{
case "Japanese" :
countryIcon = "japan.png";
break;
case "Spanish" :
countryIcon = "spain.png";
break;
default:
break;
}
<img src="#Configuration["BlobStorage:StorageUrl"]/images/#countryIcon" alt="#culture.Text"/>
</button>
</li>
}
</ul>
</form>
BaseController.cs
[Route("[controller]/[action]")]
public class BaseController : Controller
{
[HttpGet]
public IActionResult GetCartViewComponent()
{
return ViewComponent("Cart");
}
[HttpPost]
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1)}
);
return LocalRedirect(returnUrl);
}
}

Related

.NET how to disable FluentValidation with formHelper on certain submit button

I'm using FluentValidation with FormHelper in order to validate my form using ajax.
My form has 2 submit buttons:
Default one that should run validation (Save method in controller has [FormValidator] annotation)
Second one (calls another controlers' method) should have validation disabled (Save method in controller has no validation annotation)
At this moment both buttons do POST call to Save method so validation run in both cases. If I remove FormHelper validation tags, client calls correct methods but there is no validation off course.
What should I do in order to enable validation only for one button?
View:
<form id="myFrom" asp-controller="MyController" asp-action="Save" method="post" asp-formhelper="true">
<label asp-for="MyField" class="form-label">My field: </label>
<input asp-for="MyField" class="form-control"/>
<span asp-validation-for="MyField" class="text-danger"></span>
<input type="submit" value="POST with validation">
<input type="submit" asp-controller="MyController" asp-action="AnotherAction" value="Post without validation">
</form>
Controller:
public class MyController
{
[HttpPost, FormValidator]
public async Task<IActionResult> Save(MyViewModel vm)
{
//do sth
}
[HttpPost]
public async Task<IActionResult> AnotherAction(MyViewModel vm)
{
//do another actions
}
}

Why does validation in ASP.NET Core Razor Pages doesn't accept empty inputs?

I am learning how to code in ASP.NET Core Razor Pages and am working on validation right now. Both in client side and server side validation no input is allowed to be empty even though I am not using the [Required] data annotation and I am looking for a solution to that. The C# code of the page is below:
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
[BindProperty]
public int number { get; set; }
public void OnGet()
{
}
public IActionResult OnPost()
{
if(!ModelState.IsValid)
{
return Page();
}
Console.WriteLine("It works.");
return Page();
}
}
The HTML code of this page is below:
<form method="post">
<label asp-for="#Model.number">Number: </label><br />
<input asp-for="#Model.number" />
<span asp-validation-for="#Model.number" class="text-danger"></span>
<br /><br />
<button class="btn btn-primary" type="submit">Submit</button>
</form>
#section Scripts
{
}
Inside the Scripts section I use partial to load the Validation Scripts Partial. It just won't load there as code for some reason when I paste it there.
It is only a simple code that is meant to take one integer from the user as I am just trying to get it to accept an empty value. I am using .NET 6.0 and Visual Studio Community Edition 2022. Does anyone know a solution to that problem?
Because the int type does not accept null value, to solve your problem, change the code as follows To accept the amount of null as well
[BindProperty]
public int? number { get; set; }

Creating a Login Page in ASP.NET Core MVC

I am trying to create a login page. But it's saying no page found.
I have included this in Startup.cs:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{Controller=LoginController}/{action=Login}");
});
The Login.cshtml page is in views as Views/Login/Login.cshtml
and the controller lies under the Controllers folder with the name LoginController.cs
The code for LoginController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace AdminControl.Controllers
{
public class LoginController : Controller
{
// POST: LoginController
[HttpPost]
public IActionResult Login()
{
return View();
}
}
}
Login.cshtml:
<form asp-action="Users/Index" class="form-group" id="login-form" method="post" runat="server" style="text-align: center;">
<input class="form-control" placeholder="ID" runat="server" style="text-align: center;"/>
<input class="form-control" placeholder="Password" runat="server" style="text-align: center;" type="password"/>
</form>
<button class="btn btn-info" form="login-form" value="Submit" type="submit">Submit</button>
What has gone wrong?
Fix the error in a pattern: "{Controller=LoginController}/{action=Login}");
pattern: "{Controller=Login}/{action=Login}");
But it's better to use a standard default pattern
pattern: "{controller=Home}/{action=Index}/{id?}");
and redirect from Index action to login controller after checking if user hasn't logined yet
And as #IsmailDiari noticed, you should have two actions - one to get login form, another to post login form. But you also need a Model:
public class LoginViewModel
{
public string Login {get; set;}
public string Password {get; set;}
}
After this change controller and views like this:
[HttpGet]
public IActionResult Login()
{
var viewModel=new LoginViewModel;
return View(viewModel);
}
// POST: When submitting the login credentials
[HttpPost]
public IActionResult Login(LoginViewModel viewModel)
{
}
LoginForm:
#model LoginViewModel
#using (Html.BeginForm())
{
<input class="form-control" asp-for = "Login" name="login" placeholder="Login" runat="server" style
="text-align: center;"/>
<input class="form-control" asp-for="Password" name="password" placeholder="Password" runat="server" style="text-align: center;" type="password"/>
<input type="submit" value="Login" class="btn btn-primary" />
}
Please see this link to know how to set a default route, you are doing that wrong.
Also you need to provide Get action to return the view.
your controller should be like the following
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace AdminControl.Controllers
{
public class LoginController : Controller
{
// Get: Return Login View
[HttpGet]
public IActionResult Login()
{
return View();
}
// POST: When submitting the login credentials
[HttpPost]
public IActionResult Login(FormCollection collection)
{
//1. validation Goes here
//2. redirection after validation,
//return View();
}
}
}
and your cshtml body should be something like this
<body>
#using (Html.BeginForm())
{
<input class="form-control" name="id" placeholder="ID" runat="server" style
="text-align: center;"/>
<input class="form-control" name="password" placeholder="Password" runat="server" style="text-align: center;" type="password"/>
<input type="submit" value="Login" class="btn btn-primary" />
}
</body>
you should set name attribute to your input so you can find them later in your FormCollection

_ManageNav doesn't work after add my own Identity class

I used Identity in .NET Core 2.1. MVC app and I have one problem. As long as I used build-in class IdentityUser, my _ManageNav works good: when I clicked on UserName, on the left side of my application I saw ManageNav menu. But I must implicate my own class, because I must have list for users:
public class MyAppUser : IdentityUser
{
public ICollection<Recipe> Recipes { get; set; }
}
Of course I change in Startup information about class:
services.AddIdentity<MyAppUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Also add a mail sender, because I had error with this:
services.AddTransient<IEmailSender, MyAppMailSender>();
My _ManaveNav is practically build-in, I didn't have to change anything because first I wanted configure my own class, this is my _ManageNav:
#inject SignInManager<IdentityUser> SignInManager
#{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<div class="menu-left">
<ul class="nav nav-pills nav-stacked">
<li class="#ManageNavPages.IndexNavClass(ViewContext)"><a asp-page="./Index">Profile</a></li>
<li class="#ManageNavPages.ChangePasswordNavClass(ViewContext)"><a id="change-password" asp-page="./ChangePassword">Password</a></li>
#if (hasExternalLogins)
{
<li class="#ManageNavPages.ExternalLoginsNavClass(ViewContext)"><a id="external-login" asp-page="./ExternalLogins">External logins</a></li>
}
<li class="#ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)"><a asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
<li class="#ManageNavPages.PersonalDataNavClass(ViewContext)"><a asp-page="./PersonalData">Personal data</a></li>
</ul>
</div>
I tried with IdentityUser or MyAppUser in first line (#inject), but always I have default menu, which I have normal in app. My Identity layout:
#{
Layout = "/Areas/Identity/Pages/_Layout.cshtml"; }
<h2>Manage your account</h2>
<partial name="_MenuTop" />
<div class="all">
<partial name="_ManageNav" />
<div class="content">
#RenderBody()
</div>
<partial name="_MenuRight" />
<div class="clearfix"></div>
</div>
#section Scripts {
#RenderSection("Scripts", required: false)
}
Maybe one of You have this problem in Your app? I think ManageNav and Layout is OK, because on version where I use IdentityUser ManageNav works great.
Best regards,
Kamil
I assume, you should use your custom class here:
#inject SignInManager<MyAppUser> SignInManager
instead of:
#inject SignInManager<IdentityUser> SignInManager
Add _ViewStart.cshtml file in the Areas/Identity/Pages/Account/Manage folder with the following content:
#{
Layout = "_Layout";
}

Post back view values to controller MV3

I am new to mvc 3. i am trying to create a view and controller. my scenario is that i have controller which has two actions. while i call first action inside controller i have to load default values to view. inside view i have a buttons to post back values. so while click one of the button inside view, it has to go second action in same controller and has to return back to same view with success or failure message.
My view is like this
#using (Html.BeginForm())
{
<table>
<tr>
<td>
#Html.TextBox("txtTitle")
</td>
<td>
<input type="button" value="Go Somewhere Else" onclick="location.href='#Url.Action("Postback", "Default1")'" />
</td>
</tr>
</table>
}
My Controller,
public class Default1Controller : Controller
{
//
// GET: /Default1/
public ActionResult Index()
{
// has to default load value to text box
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Postback(FormCollection obj)
{
// has to take value from txt box
object obj1 = Request["txtTitle"];
return View("Index");
}
}
My problem if call Postback action from any other view it works. but inside same view if i click on button the error shows like "http://localhost:14953/Default1/Postback".
What is solution here? i expect to navigate to same controller as well as to other controller inside same form and return to same view.
As reference to this article.....
public class HttpParamActionAttribute : ActionNameSelectorAttribute {
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo) {
if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
return true;
if (!actionName.Equals("Action", StringComparison.InvariantCultureIgnoreCase))
return false;
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
}
}
In view form like this...
#using (Html.BeginForm("Action", "Post")) {
<!— …form fields… -->
<input type="submit" name="SaveDraft" value="Save Draft" />
<input type="submit" name="Publish" value="Publish" />
}
and actions in controller...
public class PostController : Controller {
[HttpParamAction]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SaveDraft(…) {
//…
}
[HttpParamAction]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Publish(…) {
//…
}
}
You can specify an ActionName to your Postback method like this:
[ActionName("Index")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Postback(FormCollection obj)...
So F5 repost the same values and calling the URL directly from the address bar returns the View as expected.
There is a constructor for Html.BeginForm Helper method which takes action name and controller name. Use that
public static MvcForm BeginForm(
this HtmlHelper htmlHelper,
string actionName,
string controllerName
)
http://msdn.microsoft.com/en-us/library/dd492590.aspx
So your form should look like this. You dont need the onclick function in your input element.
#using (Html.BeginForm("Postback", "Default1"))
{
//Your elements
<input type="button" value="Go Somewhere Else" />
}
That will render the HTML markup like this
<form action="/Default1/Postback" method="post">
// Your elements
<input type="button" value="Go Somewhere Else" />
</form>
If you want multiple submit button in the same form, It is answered here
I think we can specific action and controller at Html helper.
#using (Html.BeginForm("Postback", "Default1"))
{
<table>
<tr>
<td>
#Html.TextBox("txtTitle")
</td>
<td>
<input type="submit" value="Go Somewhere Else" />
<!-- the input should be submit?-->
</td>
</tr>

Categories

Resources