I was wondering if anybody could suggest a way of overcoming the following issue. I have the following models
// AModel
public class AModel
{
public string PropOne { get; set; }
public bool PropTwo { get; set; }
public string Keyword { get; set; }
}
public class AModelViewModel
{
public AModelViewModel()
{
AModel = new AModel();
}
public AModel AModel { get; set; }
}
//BModel
public class BModel
{
public string PropOne { get; set; }
public bool PropTwo { get; set; }
public string Keyword { get; set; }
}
public class BModelViewModel
{
public BModelViewModel()
{
BModel = new BModel();
}
public BModel BModel { get; set; }
}
This is what my controller looks like
public ActionResult PageA()
{
var model = new AModelViewModel();
return View(model);
}
[HttpPost]
public ActionResult PageA(AModel aModel)
{
return View();
}
public ActionResult PageB()
{
var model = new BModelViewModel();
return View(model);
}
[HttpPost]
public ActionResult PageB(BModel bModel)
{
return View();
}
The two views look like this
//PageA
#model WebApplication13.Models.AModelViewModel
#using (Html.BeginForm())
{
<div class="form-group">
<label>Title</label>
<input type="title" class="form-control" id="title" name="Model.PropOne">
</div>
<div class="checkbox">
#Html.CheckBoxFor(x => x.Model.PropTwo)
</div>
#Html.Partial("~/Views/Shared/_RandomView.cshtml")
<button type="submit" class="btn btn-default">Submit</button>
}
//PageB
#model WebApplication13.Models.BModelViewModel
#using (Html.BeginForm())
{
<div class="form-group">
<label>Title</label>
<input type="title" class="form-control" id="title" name="Model.PropOne">
</div>
<div class="checkbox">
#Html.CheckBoxFor(x => x.Model.PropTwo)
</div>
#Html.Partial("~/Views/Shared/_RandomView.cshtml")
<button type="submit" class="btn btn-default">Submit</button>
}
Both views use the following partial view
//_RandomView
<div class="form-group">
<label for="keyword">Keyword</label>
<input type="text" class="form-control" name="Keyword" />
</div>
The problem I have is that because this partial is shared, and the name attribute of the keyword input is 'Keyword', when I submit the form on either page the keyword property is never binded so it's always null. Is there a way I can share this partial, but alter the prefix depending on what page i'm using. Any help would be greatly appreciated.
Instead of using a paritial view.. Create an EditorTemplate in Views\Shared\EditorTemplates named Keyword.cshtml with code something like
#model string
//_RandomView
<div class="form-group">
<label for="keyword">Keyword</label>
#Hmlt.TextBoxFor(a => a, new {class="form-control"})
</div>
then in your views you just call
#Html.EditorFor(a => a.AModel.Keyword,"Keyword")
if you have to use PartialView.. you can pass in an HtmlFieldPrefix via ViewDataDictionary when you call the partial view. To do this, you would call your partial like this.
#Html.Partial("~/Views/Shared/_RandomView.cshtml",
new ViewDataDictionary { TemplateInfo = new TemplateInfo() {
HtmlFieldPrefix = "AModel" } });
then in your partial view you will need to use the #Html.Textbox helper like so.
<div class="form-group">
<label for="keyword">Keyword</label>
#Html.TextBox("Keyword", string.Empty, new { #class="form-control" })
</div>
Related
I'm using ASP .NET Core 5 Razor Pages.
My goal is to has a set of Partial Views (for reusability purpose) that I can use on several pages. Each Partial View has a form with its own custom post event handler (it will be processed by a code-behind of a pages that will contain this Partial View).
N.B. Some pages can contain two or even more different Partial Views! And I need that Partial View models to be validated independently of each other (in two separate custom event handlers).
Here is simplified code that I use for today. Partial View model (contains some data for a user):
public partial class User
{
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
}
public class UserModel : PageModel
{
[BindProperty]
public User user { get; set; }
[TempData]
public string StatusMessage { get; set; }
public UserModel()
{
user = new User();
}
}
_UserPartial.cshtml (displays that user data):
#model UserModel
<div class="row text-warning">
<div class="col-md-4">
<form method="post" asp-page-handler="UserEdited">
<div asp-validation-summary="ModelOnly"></div>
<div class="form-group">
<label asp-for="user.Surname" class="control-label"></label>
<input asp-for="user.Surname" class="form-control" />
<span asp-validation-for="user.Surname" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="user.Name" class="control-label"></label>
<input asp-for="user.Name" class="form-control" />
<span asp-validation-for="user.Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save user data" />
</div>
</form>
</div>
</div>
Index.cshtml (main page that contains Partial View):
#page
#model IndexModel
#{
ViewData["Title"] = "Main page";
}
#if (!String.IsNullOrWhiteSpace(#Model.StatusMessage))
{
<div class="text-center">
<h4 class="text-warning">#Model.StatusMessage</h4>
</div>
}
<div class="text-center" id="mainView">
<p>Some text in a main view</p>
<p>Some link in a main view.</p>
</div>
<div class="text-center" id="userPartialView">
#{await Html.RenderPartialAsync("_UserPartial", IndexModel.userModel);}
</div>
//Some other Partial View (which contains some data for a message)
<div class="text-center" id="userPartialView">
#{await Html.RenderPartialAsync("_MessagePartial", IndexModel.messageModel);}
</div>
Index.cshtml.cs (code-behind of a main page):
public class IndexModel : PageModel
{
public static UserModel userModel { get; set; }
//A model for some other Partial View (which contains some data for a message)
public static MessageModel messageModel { get; set; }
[TempData]
public string StatusMessage { get; set; }
public IActionResult OnGet()
{
userModel = new UserModel();
messageModel = new MessageModel();
return Page();
}
public IActionResult OnPostUserEdited()
{
if (!userModel.ModelState.IsValid)
{
return Page();
}
StatusMessage = "User data was saved!";
return RedirectToPage();
}
}
Problem is that userModel.ModelState is always valid even if Name and Surname are empty:
Looks like UserModel is not validaiting at all.
And I have a strong feeling that I'm using Partial Views tha wrong way (not the way they were supposed to be used).
So what's wrong with my code? How to properly use Partial View and validate it's model state? Any help is appreciated.
You don't need to have a Page Model for the partial view. Just add it as a Razor View.
Index.cshtml.cs
[BindProperty]
public User userModel { get; set; }
[BindProperty]
public Message messageModel { get; set; }
[TempData]
public string StatusMessage { get; set; }
public void OnGet()
{
userModel = new User();
}
public IActionResult OnPostUserEdited()
{
ModelState.Clear();
if (!TryValidateModel(userModel))
{
return Page();
}
StatusMessage = "User data was saved!";
return RedirectToPage();
}
public IActionResult OnPostMessageEdited()
{
ModelState.Clear();
if (!TryValidateModel(messageModel))
{
return Page();
}
StatusMessage = "Message data was saved!";
return RedirectToPage();
}
Index.cshtml:
<div class="text-center" id="userPartialView">
#{await Html.RenderPartialAsync("_UserPartial", Model.userModel);}
</div>
<div class="text-center" id="messagePartialView">
#{await Html.RenderPartialAsync("_MessagePartial", Model.messageModel);}
</div>
I'm using custom templates within my MVC project to display various object types in different ways. Some are working and others are not! For any that don't work, they are being passed into my object.cshtml custom template rather than their own.
Here's an example. In this example, I'm creating an address lookup type which I want to render a first line of address and postcode field with a lookup button.
My ViewModel has the following:
namespace MyProject.Views
{
public class AddressLookup
{
public string Postcode { get; set; }
public string FirstLine { get; set; }
}
public class RegistrationViewModel
{
[DisplayName("Address Lookup")]
public AddressLookup addressLookup { get; set; }
}
}
My view looks like this:
#model RegistrationViewModel
<div class="well" id="form-well">
<h2>Register New User</h2>
<h3>Step 1 of 3 - Login Details</h3>
#using (Html.BeginForm("RegisterNewUser", "Controller", FormMethod.Post, new { #class = "form-horizonal" }))
{
#Html.AntiForgeryToken();
#Html.EditorForModel(Model);
#Html.ValidationSummary();
<div style="padding: 15px;" class="form-group col-md-offset-3">
<input type="submit" value="Next" class="btn btn-lg btn-success" />
</div>
}
</div>
My AddressLookup.cshtml looks like this:
#using MyProject
#model AddressLookup
#Html.TextBoxFor(x => x.FirstLine)
#Html.TextBoxFor(x => x.Postcode)
<p>
<button class="btn btn-info" type="button" onclick="alert('lookup');" value="Add new address">Lookup address</button>
</p>
But when debugging, it runs the code in the object.cshtml in the EditorTemplates folder and not the one for AddressLookup.
Any thoughts?
Thanks
Simon
Adding a UIHint to the property in my view model worked (although I don't fully understand why).
namespace MyProject.Views
{
public class AddressLookup
{
public string Postcode { get; set; }
public string FirstLine { get; set; }
}
public class RegistrationViewModel
{
[DisplayName("Address Lookup")]
[UIHint("AddressLookup")]
public AddressLookup addressLookup { get; set; }
}
}
Thanks
Simon
The issue here is that upon submitting the form, the list of UserCustomerClaimsproperties in the ApplicationUserEditViewModel object is null; the responses of the check boxes in the for loop are not being or making it to the controller.
View
#model WaterManagementSystem.Features.Admin.ApplicationUserEditViewModel
#{
ViewBag.Title = "Edit User";
Layout = "_Layout";
}
<form asp-action="UpdateUser" asp-controller="Admin" method="post">
...
<div style="color: White;">
#foreach (var userClaim in Model.UserCustomerClaims)
{
<div>
<input type="checkbox" asp-for="#userClaim.HasClaim" id="#userClaim.ID" name="#userClaim.ID" checked="#(userClaim.HasClaim)" />
<input type="hidden" asp-for="#userClaim.ID" /> #userClaim.UserClaim.Type
</div>
}
</div>
<div>
<div>
<button type="submit" class="btn btn-default">Save</button>
</div>
</div>
</form>
View Model
public class ApplicationUserEditViewModel : ApplicationUserViewModel
{
...
private List<UserCustomerClaim> userCustomerClaims_ = null;
public ApplicationUserEditViewModel() : this(new ApplicationUser())
{
userCustomerClaims_ = new List<UserCustomerClaim>();
}
public List<UserCustomerClaim> UserCustomerClaims
{
get
{
return userCustomerClaims_;
}
}
public class UserCustomerClaim
{
public UserCustomerClaim(Claim userClaim, bool hasClaim)
{
UserClaim = userClaim;
HasClaim = hasClaim;
ID = Guid.NewGuid();
}
public Guid ID { get; set; }
public Claim UserClaim { get; set; }
public bool HasClaim { get; set; }
}
}
Controller
[HttpPost]
public async Task<ActionResult> UpdateUser(ApplicationUserEditViewModel vm)
{
...
}
The proposed duplicate does not use the asp-for tag helper.
I am currently reading values in a configuration file and setting the values to a view model. I am displaying them on the UI in textboxes. I want the user to be able to edit/change the value in the textbox and be able to hit a save button and for the changes to be saved to the view model and the configuration file. I understand there needs to be some type of Get/Post method in the controller but I'm not entirely sure how the controller should look. I am not connecting it to a database at all.
View:
#using (Html.BeginForm())
{
<fieldset>
<div class="row">
<div class="col-md-1">Logging Directory:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.loggingDirectory)</div>
<div class="col-md-1">Archive Directory:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.archiveDirectory)</div>
<div class="col-md-1">Time Between Alarms:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.timeBetweenAlarms)</div>
<div class="col-md-1">Time to Archive Logs:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.timeToArchive)</div>
<div class="col-md-1">Situator IP:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.situatorIP)</div>
<div class="col-md-1 ">Situator Port:</div>
<div class="col-md-2 ">#Html.EditorFor(model => Model.situatorPort)</div>
<div class="col-md-1 ">Clean Up:</div>
<div class="col-md-2 ">#Html.EditorFor(model => Model.timeToCleanUp)</div>
<div class="col-md-1 ">Coorelation Zone:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.coorelationZone)</div>
</div>
<div class="row submitButton">
<button class="btn btn-primary" type="submit">Save</button>
</div>
</fieldset>
}
View Model
public class ConfigurationViewModel
{
public string loggingDirectory { get; set; }
public string archiveDirectory { get; set; }
public string situatorIP { get; set; }
public string situatorPort { get; set; }
public string timeBetweenAlarms { get; set; }
public string timeToArchive { get; set; }
public string sightlogixIP { get; set; }
public string timeToCleanUp { get; set; }
public string coorelationZone { get; set; }
}
Controller:
public ActionResult Index()
{
ConfigurationViewModel cvm = new ConfigurationViewModel();
cvm.loggingDirectory = ConfigurationManager.AppSettings["loggingDirectoryPath"];
cvm.archiveDirectory = ConfigurationManager.AppSettings["archiveDirectoryPath"];
cvm.situatorIP = ConfigurationManager.AppSettings["SituatorIP"];
cvm.situatorPort = ConfigurationManager.AppSettings["SituatorPort"];
cvm.timeBetweenAlarms = ConfigurationManager.AppSettings["TimeIncrementBetweenalarmsInSeconds"];
cvm.timeToArchive = ConfigurationManager.AppSettings["timeIncrementForArchivingLogFilesInHours"];
cvm.sightlogixIP = ConfigurationManager.AppSettings["SightLogixIP"];
cvm.timeToCleanUp = ConfigurationManager.AppSettings["timeIncrementForCleaningUp"];
cvm.coorelationZone = ConfigurationManager.AppSettings["correlationZoneLengthInFeet"];
return View(cvm);
}
[HttpGet]
public ActionResult Edit()
{
return;
}
[HttpPost]
public ActionResult Edit()
{
return;
}
Pass view model in Get Edit method
[HttpGet]
public ActionResult Edit()
{
ConfigurationViewModel cvm = new ConfigurationViewModel();
cvm.loggingDirectory = ConfigurationManager.AppSettings["loggingDirectoryPath"];
cvm.archiveDirectory = ConfigurationManager.AppSettings["archiveDirectoryPath"];
cvm.situatorIP = ConfigurationManager.AppSettings["SituatorIP"];
cvm.situatorPort = ConfigurationManager.AppSettings["SituatorPort"];
//...
return View(cvm);
}
Send updated view model to post edit method and perform action on it
[HttpPost]
public ActionResult Edit(ConfigurationViewModel cvm)
{
ConfigurationManager.AppSettings["archiveDirectoryPath"] = cvm.archiveDirectory;
ConfigurationManager.AppSettings["SituatorIP"] = cvm.situatorIP;
ConfigurationManager.AppSettings["SituatorPort"]= cvm.situatorPort;
//...
return View(cvm);
}
And your razor view which will submit updated data to your Post Edit method
#using (Html.BeginForm("Edit", "Your controller", FormMethod.Post))
{
....
}
I have:
public class Nomenclature
{
public virtual int NomenclatureId { get; set; }
public virtual NomenclatureType NomenclatureType { get; set; }
public virtual IDictionary<NomenclatureAttribute, string> Attributes { get; set; }
}
public class NomenclatureType
{
public virtual int NomenclatureTypeId { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Nomenclature> Nomenclatures { get; set; }
public virtual ICollection<NomenclatureAttribute> NomenclatureAttributes { get; set; }
public NomenclatureType()
{
Nomenclatures = new HashSet<Nomenclature>();
NomenclatureAttributes = new HashSet<NomenclatureAttribute>();
}
}
public class NomenclatureAttribute
{
public virtual int NomenclatureAttributeId { get; set; }
public virtual string AttributeName { get; set; }
public virtual string AttributeType { get; set; }
public virtual NomenclatureType NomenclatureType { get; set; }
}
it's all represents a nomenclature of goods in my application. I'am tryin create new Nomenclature in my app. I use NHibernate. I create controller and create action:
[HttpGet]
public ActionResult Create(string nomenclatureType)
{
if (nomenclatureType == null)
return RedirectToAction("List", "Nomenclature");
ViewData["NomenclatureAttributes"] =
_repositoryNomenclatureType.Get(w => w.Name == nomenclatureType).NomenclatureAttributes.ToList();
return View();
}
[HttpPost]
public IActionResult Create(Nomenclature nomenclature)
{
try
{
if (ModelState.IsValid)
{
_repositoryNomenclature.Create(nomenclature);
return RedirectToAction("List", "Nomenclature");
}
}
catch (Exception)
{
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists see your system administrator.");
}
return View(nomenclature);
}
I need to foreach all NomenclatureAttrributes specified for any Nomenclature Type and create editors for all values and add all to Model.Attributes.
#model Nomenclature
#{
ViewBag.Title = "New nomenclature";
Layout = "_Layout";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
#foreach (var a in (List<NomenclatureAttribute>)ViewData["NomenclatureAttributes"])
{
<div class="form-group">
<label class="control-label col-md-2">#a.AttributeName</label>
<div class="col-md-10">
**What i should place to this???**
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
I use Asp.net core web application (.NET Framework)
First Create ViewModel.
public class CreateNomenclatureViewModel
{
//Add other properties if needed
public NomenclatureType SelectedNomenclatureType { get; set; }
public List<NomenclatureAttribute> Attributes { get; set;}
}
Second
[HttpGet]
public ActionResult Create(string nomenclatureType)
{
if (nomenclatureType == null)
return RedirectToAction("List", "Nomenclature");
var viewModel= new CreateMomenClatureViewModel
{
Attributes = _repositoryNomenclatureType.Get(w => w.Name == nomenclatureType).NomenclatureAttributes.ToList()
}
return View(viewModel);
}
Than fix your view
#model CreateNomenclatureViewModel
#{
ViewBag.Title = "New nomenclature";
Layout = "_Layout";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
#if (Model != null && Model.Attributes != null)
{
for (int i = 0; i < Model.Attributes.Count; i++)
{
<div class="form-group">
#Html.DisplayFor(modelItem => Model.Attributes [i].AttributeName)
#Html.TextBoxFor(modelItem => Model.Attributes [i].AttributeType )
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
if you want to use Nomenclature as ViewModel you can create new Nomenclature on Get Method than pass to view in razor view.
<div class="form-group">
#Html.DisplayFor(modelItem => Model.Attributes.Keys.ElementAt(i).AttributeName)
#Html.TextBoxFor(modelItem => Model.Attributes.Keys.ElementAt(i).AttributeType )
</div>