I have the following form in a .NET Core application, consisting of a text input field and a "Submit" button.
I'd like the text from the text input field to re-appear in the form after submission, being passed to the controller action from the form and then being passed back to the view.
However, when I test the application, although the inputted values from the view appear when they are bound to the model in the controller, when they are passed back to the view they are wiped and I receive an "Object reference set to null" exception error.
I wondered if there's something missing from my code or what the potential cause of this may be?
Any advice would be great here,
Thanks,
Robert
// This is my view, featuring a simple form
// Values from the view are successfully being passed into the Controller
// This is the exception I receive when the values are passed back to the view:
My code:
#page
#model Models.StringModel
<div class="text-left">
<form asp-controller="Home" asp-action="Alter">
<span class="form-control">
<label asp-for="Name">Alter string:</label>
#if (#Model != null)
{
<input type="text" asp-for="Name" class="changeString" value="#Model.Name"/>
} else
{
<input type="text" asp-for="Name" class="changeString"/>
}
<input class="btn btn-primary" type="submit" value="Update" action="Update" />
</span>
</form>
</div>
StringModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SplitString.Models
{
public class StringModel
{
public int ID { get; set; }
public string Name { get; set; }
}
}
HomeController.cs
using Microsoft.AspNetCore.Mvc;
using SplitString.Models;
using System.Threading.Tasks;
namespace SplitString.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Alter([Bind("Id, Name")] StringModel stringModel)
{
stringModel.ID = 1;
return View("~/Pages/Index.cshtml", stringModel);
}
}
}
Thanks,
Robert
It looks like you are using a razor page project,so that you don't need to use mvc in it.You only need to use page handler:
Index.cshtml:
#page
#model IndexModel
<div class="text-left">
<form method="post">
<span class="form-control">
<label asp-for="stringModel.Name">Alter string:</label>
#if (#Model != null)
{
<input type="text" asp-for="stringModel.Name" class="changeString" value="#Model.stringModel.Name"/>
} else
{
<input type="text" asp-for="stringModel.Name" class="changeString"/>
}
<input class="btn btn-primary" type="submit" value="Update" action="Update" />
</span>
</form>
</div>
Index.cshtml.cs:
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
[BindProperty]
public StringModel stringModel { get; set; } = new StringModel();
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
public void OnPost()
{
stringModel.ID = 1;
//you can do something here
}
}
result:
By looking at this part of your code:
#if (#Model != null)
{
<input type="text" asp-for="Name" class="changeString" value="#Model.Name"/>
}
I saw that you are trying to access the Name property without checking if it has any value.
If you change your code to this it shouldn't throw an exception anymore.
#if (#Model != null && !string.IsNullOrEmpty(Model.Name))
{
<input type="text" asp-for="Name" class="changeString" value="#Model.Name"/>
}
please remove #page from your .cshtml file.
If you use View Engine to render cshtml, please don't use #page.
If you want to use #page, please use razor pages.
// #page
#model Models.StringModel
<div class="text-left">
<form asp-controller="Home" asp-action="Alter">
<span class="form-control">
<label asp-for="Name">Alter string:</label>
#if (#Model != null)
{
<input type="text" asp-for="Name" class="changeString" value="#Model.Name"/>
} else
{
<input type="text" asp-for="Name" class="changeString"/>
}
<input class="btn btn-primary" type="submit" value="Update" action="Update" />
</span>
</form>
</div>
Related
I have a method to add new roles like:
public async Task<IActionResult> CreateRole(ApplicationRoleModel model)
{
try
{
foreach (var role in model.RoleList)
{
var roleExists = await _roleManager.RoleExistsAsync(role.Name);
if (roleExists) continue;
var createRole = _roleManager.CreateAsync(role);
}
return Ok();
}
catch (Exception e)
{
return BadRequest();
}
}
So as you can see I use foreach clause to access model who have IEnumerable of ApplicationRole like:
public class ApplicationRoleModel : ClaimsToRoleModel
{
public IEnumerable<ApplicationRole> RoleList { get; set; }
}
So as you can see in foreach I can access to role.Name of RoleList (ApplicationRole), I tested it with postman and it works, it added new Role successfull, now instead postman I want to create view to create new role so I try:
#page
#model Security.Dto.Models.ApplicationRoleModel
<form asp-controller="security" asp-action="CreateRole" method="post">
<h4>Create new Role</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="RoleList.Name">Role Name</label>
<input asp-for="RoleList.Name" class="form-control" />
</div>
<button type="submit" class="btn btn-default">Create</button>
</form>
But when I try to compile project it return:
'IEnumerable' does not contain a definition for
'Name' and no extension method 'Name' accepting a first argument of
type 'IEnumerable' could be found (are you missing a
using directive or an assembly reference?)
So, how can I access to Name property of ApplicationRole to assign to model from view? Regards
Note: I also try to access it on my view like:
#for(Int32 i = 0; i < this.Model.RoleList.Count; i++ ) {
<div class="form-group">
<label asp-for="RoleList[i].Name">Role Name</label>
<input asp-for="RoleList[i].Name" class="form-control" />
</div>
}
But I get:
Cannot apply indexing with [] to an expression of type
'IEnumerable'
The model in your view is IEnumerable<ApplicationRole> but you attempting to create controls for a single instance of ApplicationRole
You could use List<ApplicationRole> instead of IEnumerable<ApplicationRole>in your model and use jquery to add role lists as below
Model:
public class ApplicationRoleModel : ClaimsToRoleModel
{
public List<ApplicationRole> RoleList { get; set; }
}
View:
#page
#model Security.Dto.Models.ApplicationRoleModel
<form asp-controller="security" asp-action="CreateRole" method="post">
<h4>Create new Role</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group" id="item-list">
<label>Role Name</label>
<input asp-for="RoleList[0].Name" class="form-control items" />
</div>
Add another//click to add
<br />
<button type="submit" class="btn btn-default">Create</button>
</form>
#section Scripts {
<script>
$(function () {
$("#add").click(function (e) {
e.preventDefault();
var i = ($(".items").length);
var n = '<input class="form-control items" name="RoleList[' + i + '].Name" />'
$("#item-list").append(n);
});
});
</script>
}
The problem that I have faced lately when trying to write a site on razor pages - when I use variables in a class method, it doesn't keep that data inside. In example: I have a method which creates data when the page is created. And when I press submit button: The data isn't remembered inside the class and thus it returns null.
I've tried to work with Data binding, Temp data, Private classes. Neither of them kept data for a future use inside one class. The current code is:
`
namespace TestSite.Pages.Shared
{
public class Registration_2Model : PageModel
{
private readonly TestSite.Data.ApplicationDbContext _context;
public UserManager<IdentityUser> _user;
public string _ID { get; set; }
public string _Code { get; set; }
public bool _Validated;
public Registration_2Model(UserManager<IdentityUser> UserManager, ApplicationDbContext context)
{
_context = context;
_user = UserManager;
}
public IActionResult OnGet()
{
var CurrentUser = _context.Simple_User.FirstOrDefault(m => m.ID == Guid.Parse(_user.GetUserId(User)));
if (CurrentUser == null)
{
_ID = _user.GetUserId(User);
_Code = GenerateCode();
_Validated = false;
TempData["ID"] = _ID;
TempData["Code"] = _Code;
return Page();
} else { return Redirect("/Index"); }
}
[BindProperty]
public Simple_User Simple_User { get; set; }
public async Task<IActionResult> OnPostAsync()
{
Simple_User.ID = Guid.Parse((string)TempData["ID"]);
Simple_User.Code = (string)TempData["Code"];
Simple_User.Validated = false;
if (!ModelState.IsValid)
{
return Page();
}
_context.Simple_User.Add(Simple_User);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
private string GenerateCode()
{
Random _random = new Random();
return $"{_random.Next(1000, 9999).ToString()}-{DateTime.Now.Year}";
}
}
}
`
and HTML:
`
#{
ViewData["Title"] = "Second registration";
}
<h2>Second registration</h2>
<h4>One step left. After your initial registration, you must fill in some blanks, after which, our moderator will check and add you to our list.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label class="control-label">ID</label>
<input class="form-control" type="text" placeholder="#Model._ID" readonly />
</div>
<div class="form-group">
<label asp-for="Simple_User.Name" class="control-label"></label>
<input asp-for="Simple_User.Name" class="form-control" />
<span asp-validation-for="Simple_User.Name" class="text-danger"></span>
<span asp-validation-for="Simple_User.Code" class="text-danger"></span>
</div>
<div class="form-group">
<label class="control-label">Code</label>
<input class="form-control" type="text" placeholder="#Model._Code" readonly />
</div>
<div class="form-group">
<label asp-for="Simple_User.Address" class="control-label"></label>
<input asp-for="Simple_User.Address" class="form-control" />
<span asp-validation-for="Simple_User.Address" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}`
Basically, site displays some value in an inaccessible field and should use it when it creates a Simple_User in database. But so far I got only nulls
HTTP is stateless™. If you want to set properties on objects during the execution of one request, and have those values available for a subsequent request, you need to implement your own state management strategy.
There are a lot of options available to you, depending on what you wan to do. If security is important, you should look at session variables. In the meantime, here are all of the options available: https://www.learnrazorpages.com/razor-pages/state-management
Try scaffolding the pages. You can throw out the Create/Details/Delete pages and keep only the Edit page if that is what your looking for. You can do this from the command line or in Visual Studio when creating a new page. Something like the following from the CLI (see here for more details)
dotnet aspnet-codegenerator razorpage -m SimpleUser -dc ApplicationDbContext
Also if you want your _ID or _Code properties to be sent in the post add the [BindProperty] attribute to them. See here for more info about Model Binding.
This is my form:
#using (Html.BeginForm())
{
<h3 class="text-blue title-article editable">thisStr</h3>
<input type="submit" value="xxxx" class="btn btn-success" name="xxxx"/>
}
#model.title is part of an inline-editable that i can change to whatever, what i need to do is
pass the string in the h3 to this controller-method:
public ActionResult method(string test)
{
someProp = test;
return View();
}
I think my problem is this line:
<input type="submit" value="xxxx" class="btn btn-success" name="xxxx"/>
Can someone help me out, thanks!
Put this in the form:
#Html.HiddenFor(x=>x.Title)
view:
#using (Html.BeginForm("YourAction","YourController",FormMethod.Post))
{
<h3 class="text-blue title-article editable">#Model.Title</h3>
#Html.HiddenFor(x=>x.Title)
<input type="submit" value="xxxx" class="btn btn-success" name="xxxx"/>
}
in your action:
[HttpPost]
public ActionResult YourAction(FormCollection form)
{
sting vl= form["Title"].ToString();
return View();
}
Add a #Html.HiddenFor((p) => Model.Title) inside your form, which will then submit the text back with the POST for you.
This is my form
#using (Html.BeginForm("EditPayments", "BookingPathLabelsCms"))
{
if (#Model.DisplayName == "Payment Labels")
{
<textarea id="seeit" name="seeit" rows="5" cols="10"></textarea>
<textarea id="seeitNoSelect" name="seeitNoSelect" rows="5" cols="10"></textarea>
<div class="cmsButtonContainer">
Cancel it
<input type="submit" name="Save" value="Save it"#* onmouseover="copyto();"*# />
</div>
}
}
And this is my controller action
public ActionResult EditPayments(BookingPathLabelsCmsViewModel model)
{
string txtarea = Request.Form["seeit"];
return RedirectToAction("Index");
}
Am not getting the values of textareas here,but values in the breakpoint ,see image.
Your code should looks like:
#using (Html.BeginForm("EditPayments", "BookingPathLabelsCms"))
{
if (#Model.DisplayName == "Payment Labels")
{
#Html.TextBoxFor(m => m.SeeIt)
#Html.TextBoxFor(m => m.SeeItNoSelect)
<div class="cmsButtonContainer">
Cancel it
<input type="submit" name="Save" value="Save it"#* onmouseover="copyto();"*# />
</div>
}
}
Of course, your ViewModel BookingPathLabelsCmsViewModel should have SeeIt and SeeItNoSelect properties. After that, MVC will bind correctly entered data.
First create a class with property.
public class TextAreaProperty
{
public string MyTextAreaValue { get; set; }
}
Use on the view declare like:
#model <project_name>.Models.<Class_name>
In this case:
#model MvcApplication1.Models.TextAreaProperty
Use this textArea Razor
#Html.TextAreaFor(x=> x.MyTextAreaValue)
On method post receiving parameter type TextAreaProperty
[HttpPost]
public ActionResult Index(TextAreaProperty textAreaProperty)
{
return View();
}
You will get the value from textAreProperty.
I have a problem to send my Model into my controller.
I use a button with the ajax to change the page but i need the model who is the first page to the second page.
I would like send my model in my controller but it's not work.
When i come in the page CreateAll, the renderpartial follow works to display Step1 but if i click on the step2 i would like the mainModel to be send to my controller use a partial view with submodel.
My model is:
public class CreateAllStep
{
public CreateStep1 Step1 { get; set; }
public CreateStep2 Step2 { get; set; }
public CreateAllStep(CreateStep1 step1, CreateStep2 step2)
{
this.Step1 = step1;
this.Step2 = step2;
}
}
My controller is(when the page start):
public ActionResult Create()
{
CreateStep1 step1=FillStep1();
CreateStep2 step2 = FillStep2();
CreateAllStep allStep = new CreateAllStep(step1, step2);
return View(allStep);
}
My controller is(when i click on the button, it's here where i would like send the model):
[HttpPost]
public ActionResult Create(String btn, CreateAllStep form)
{
if (Request.IsAjaxRequest())
{
if (btn != null)
{
if (btn == "Step1")
{
return PartialView("Step1",form.Step1);//not work
}
else if (btn == "Step2")
{
return PartialView("Step2");//work
}
else if(btn =="AllStep")
{
return PartialView("AllStep");
}
}
}
return View();
}
And my main view is :
#model SiteWebEmpty.Models.CreateAllStep
#{
ViewBag.Title = "Title";
}
<script type="text/javascript">
$(function () {
$('form').submit(function () {
$.post(this.action, $(this).serialize(), function (data) {
alert(data);
});
return false;
});
});
</script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<h2>Title</h2>
#using (Ajax.BeginForm("Create", //controller action name
"CreateStep", //controller name
new AjaxOptions //ajax options that tell mvc how to perform the replacement
{
UpdateTargetId = "ViewPage", //id of div to update
HttpMethod = "Post" //how to call the controller action
}, new { id = "FormName" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Create </legend>
<button type="submit" name="btn" value="Step1" id="Step1">Step 1</button>
<button type="submit" name="btn" value="Step2" id="Step2">Step 2</button>
<button type="submit" name="btn" value="AllStep" id="AllStep">All Step</button>
<div id="ViewPage">
#Html.Partial("Step1", Model)
</div>
</fieldset>
}
My partial view is:
#model SiteWebEmpty.Models.ArticleRequest.CreateArticle.ArticleRequestDisplayCreateAllStep
<fieldset>
<legend>Step 1</legend>
#Html.LabelFor(step1 => step1.Step1.Customer)
#Html.EditorFor(step1 => step1.Step1.Customer)
#Html.ValidationMessageFor(step1 => step1.Step1.Customer)
#Html.LabelFor(articleType => articleType.Step1.ArticleType)
#Html.DropDownList("ArticleType", Model.Step1.ArticleType)
#Html.ValidationMessageFor(articleType => articleType.Step1.ArticleType)
#Html.LabelFor(model => model.Step1.LabelType)
#Html.DropDownList("LabelType", Model.Step1.LabelType)
#Html.ValidationMessageFor(model => model.Step1.LabelType)
</fieldset>
render html:
<script src="/Scripts/jquery.unobtrusive-ajax.min.js" type="text/javascript"></script>
<h2>Title</h2>
<form action="/CreateStep/Create?Length=13" data-ajax="true" data-ajax-method="Post" data-ajax-mode="replace" data-ajax-update="#ViewPage" id="FormName" method="post"> <fieldset>
<legend>Create </legend>
<button type="submit" name="btn" value="Step1" id="Step1">Step 1</button>
<button type="submit" name="btn" value="Step2" id="Step2">Step 2</button>
<button type="submit" name="btn" value="AllStep" id="AllStep">All Step</button>
<div id="ViewPage">
<fieldset>
<legend>Step 1</legend>
<label for="Customer">Customer</label>
<input class="text-box single-line" data-val="true" data-val-required="Customer is required" id="Customer" name="Customer" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Customer" data-valmsg-replace="true"></span>
<label for="ArticleType">Article Type</label>
<select data-val="true" data-val-required="ArticleType is required" id="ArticleType" name="ArticleType"><option value="127">AR1 : New Product</option>
<option value="161">AR2 : Product Modification</option>
</select>
<span class="field-validation-valid" data-valmsg-for="ArticleType" data-valmsg-replace="true"></span>
<label for="LabelType">Label Type</label>
<select data-val="true" data-val-required="LabelType is required" id="LabelType" name="LabelType"><option value="129">Private Label</option>
</select>
<span class="field-validation-valid" data-valmsg-for="LabelType" data-valmsg-replace="true"></span>
</fieldset>
</div>
</fieldset>
</form>
Thanks for your help :) !
Could you post the final HTML?
I think that your #Html.Partial("Step1", Model.Step1) will render a input text with id like Customer or ArticleType instead of Step1.Customer and Step1.ArticleType. What will bind to CreateAllStep.Customer that doesn´t exist.
If you have the Headers sent by the browsers will help too.
Update: Change your partial Step1, to accept a CreateAllStep Model and try again
Try removing your constructor on your ViewModel:
public CreateAllStep(CreateStep1 step1, CreateStep2 step2)
{
this.Step1 = step1;
this.Step2 = step2;
}
And change the controller code to:
public ActionResult Create()
{
CreateAllStep allStep = new CreateAllStep{Step1 = FillStep1(), Step2 = FillStep2()};
return View(allStep);
}
I have run into issues with constructors with parameters when databinding.