I am new to ASP.NET MVC. Want to pass common data to all pages in the application. Following Pass data to layout that are common to all pages. Still not able to get the value in layout and in the controller.
When user logged in to system I set flag 'UserhasLoggedIn' and redirect to another controller action.In the layout, I have added checks if flag is set to false then menu items should not be displayed and LoginPage should display. When page is redirected to Home Index it doesn't get 'UserhasLoggedIn' flag.
Layout.cshtml:
#using WebApplication2.Models
#model ViewModelBase
<div class="btn-toolbar" style="background-color:dimgrey; padding-left:35px;padding-top:-10px;">
#if (null != Model && (bool)Model.IsuserLoggedIn)
{
foreach (var menuItem in Model.MenuItems)
{
<div class="btn-group">
<button class="btn btn-primary dropdown-toggle" data-toggle="dropdown">menuItem.MenuName <span class="caret"></span></button>
<ul class="dropdown-menu">
#foreach (var subMenu in menuItem.SubMenu)
{
<li>#Html.ActionLink((string)subMenu.MenuName, (string)subMenu.ActionName, (string)subMenu.ControllerName)</li>
}
</ul>
</div>
}
}
<div class="btn-group pull-right" style="padding-right:35px">
#if (null == Model || !(bool)Model.IsuserLoggedIn)
{
<button class="btn btn-primary" onclick="location.href='#Url.Action("Login", "Account")'">Log in</button>
}
else
{
<button class="btn btn-primary" onclick="location.href='#Url.Action("LogOff", "Account")'">Log out </button>
}
</div>
</div>
I have created common view model that can be used.
public abstract class ViewModelBase
{
public bool IsuserLoggedIn;
}
LoginViewModel:
public class LoginViewModel : ViewModelBase
{
[Required]
[Display(Name = "Email")]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
HomeViewModel:
public class HomeViewModel : ViewModelBase
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
AccountController Login Action:
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
return RedirectToAction("Index", "Home", new HomeViewModel() { IsuserLoggedIn = true, MenuItems=null });
}
HomeController Index action: Not able to get the IsUserLoggedIn to true and not even in the Layout.
public ActionResult Index(HomeViewModel baseModel)
{
baseModel.FirstName = "First";
baseModel.LastName = "Last Name";
return View(baseModel);
}
IsuserLoggedIn is a field, not a property and the DefaultModelBinder cannot set its value (it has no setter) so the property is initialized as false in the Index() method (the default value for bool)
Change the base view model to make it a property
public abstract class ViewModelBase
{
public bool IsuserLoggedIn { get; set; }
}
Side note: Just use #if (Model != null && Model.IsuserLoggedIn) - there is no need to cast a bool to a bool
Related
Using latest ASP.NET Core, Windows 10 Pro
TLDR: my controller is returning a "Microsoft.AspNetCore.Mvc.ModelBinding.ModelError" for an entity's parent.
Entity: Schedule
public class Schedule
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ScheduleId { get; set; }
[Required]
[Display(Name = "Start On")]
public DateTime StartDateTime { get; set; } = DateTime.Now.AddMinutes(1);
[Display(Name = "Reschedule On")]
public string Recurrence { get; set; }
[Display(Name = "Parameter 1")]
[MaxLength(256), StringLength(256)]
public string Param1 { get; set; }
// ... more properties
[Display(Name = "Parameter 7")]
[MaxLength(256), StringLength(256)]
public string Param7 { get; set; }
[Display(Name = "Message Text")]
public string MessageText { get; set; }
#region relationships
[Required]
[Display(Name = "For Process")]
public int ProcessId { get; set; }
[Required]
[Display(Name = "For Process")]
public virtual Process Process { get; set; }
public virtual ICollection<ScheduleNotification> ScheduleNotifications { get; set; }
#endregion
}
Entity: Process
public class Process : IValidatableObject
{
private int? _ApplicationId;
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ProcessId { get; set; }
[Required]
[StringLength(80, MinimumLength = 5, ErrorMessage = "Names must be between 5 and 80 characters")]
[MaxLength(80)]
public string Name { get; set; }
public string Description { get; set; }
[Required]
[Display(Name = "Process Type")]
public ProcessTypeEnum ProcessType { get; set; }
// ... more properties
[Display(Name = "Primary Application")]
public int? ApplicationId
{
get
{
return _ApplicationId;
}
set
{
_ApplicationId = value == 0 ? null : value;
}
}
public Application Application { get; set; }
public ICollection<Schedule> Schedules { get; set; }
}
Controller Action on Create Get
public IActionResult Create(int? processId)
{
// no value provided, so redirect to view that prompts for process
if (!processId.HasValue || processId == 0)
{
TempData["Error"] = "You must select a process to schedule";
return RedirectToAction("Processes");
}
var schedule = new Schedule()
{
ProcessId = processId.GetValueOrDefault(),
Process = _context.Processes.Find(processId.GetValueOrDefault()),
ScheduleNotifications = new List<ScheduleNotification>()
};
return View(schedule);
}
Razor view called on create
#model HangfireServer.Models.Schedule
#{
ViewData["Title"] = "Create";
}
<h1>Schedule</h1>
<hr />
<div class="row">
<div class="col">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="text-danger" style="direction:rtl" asp-validation-summary="All"></div>
#Html.HiddenFor(m => m.ProcessId)
#Html.HiddenFor(m => m.Process)
<div class="form-group row">
<text class="control-label col-sm-2">#Html.DisplayNameFor(m => m.Process.Name)</text>
<div class="col-sm-4">
#Html.DisplayFor(m => m.Process.Name)
</div>
<text class="text-left col-sm-6 text-secondary">Process to schedule</text>
</div>
<div class="form-group row">
<label asp-for="StartDateTime" class="control-label col-sm-2"></label>
<div class="col-sm-4">
<input asp-for="StartDateTime" class="control-label" />
<span asp-validation-for="StartDateTime" class="text-danger"></span>
</div>
<text class="text-left col-sm-6 text-secondary">Date and time to initiate process</text>
</div>
<!-- more stuff -->>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Create post controller
// POST: Schedules/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ScheduleId,StartDateTime,Recurrence,Param1,Param2,Param3,Param4,Param5,Param6,Param7,ProcessId,Process,ScheduleNotifications")] Schedule schedule)
{
if (ModelState.IsValid)
{
// save the new schedule
schedule.Process = await _context.Processes.FindAsync(schedule.ProcessId);
_context.Add(schedule);
await _context.SaveChangesAsync();
// queue the new schedule
EnqueueProcess(schedule);
TempData["Success"] = $"Process {schedule.Process.Name} has been scheduled";
return RedirectToAction(nameof(Index));
}
// display additional errors not caught in page validation
string errorMessage = "";
foreach (var modelState in ViewData.ModelState.Values)
{
foreach (var error in modelState.Errors)
{
errorMessage = String.IsNullOrEmpty(errorMessage) ? error.ToString() : "<br>" + error.ToString();
}
}
TempData["Error"] = errorMessage;
// return to current view
return RedirectToAction("Create", new { processId = schedule.ProcessId });
}
On trace, the GET is passing to the view schedule with its parent process id and process object populated. On POST
ModelState.IsValid = False
schedule.processId = 2 (or whatever the value sent from GET was
schedule.process is null (even though there's a hidden field for process in vie)
ModelState only has 1 invalid key
SubKey={Process}, Key="Process", ValidationState=Invalid
I think I figured it out - but this is just a hack, there's probably a correct way to do it
// populate process property with an instance of a process object
schedule.Process = await _context.Processes.Include(p => p.Application).FirstOrDefaultAsync(p => p.ProcessId == schedule.ProcessId);
ModelState.Clear();
Then either
ModelState.Remove("Process");
Or
TryValidateModel(schedule);
Regardless ModelState.IsValid is now true
In my domain I have this service
public class StudentService
{
private readonly IStudentRepository _studentRepository;
public StudentService(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
public StudentDto DisplayStudentInformation()
{
var objStuSec = _studentRepository.DisplayStudentSection();
return objStuSec;
}
}
Here is my studentDto
public class StudentDto
{
public string StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public List<DepartmentDto> GetAllDepartments;
}
Here is my code for the Home controller
public class HomeController : Controller
{
private StudentService _objStudentService;
public HomeController(StudentService objStudentService)
{
_objStudentService = objStudentService;
}
public ActionResult Index()
{
ViewBag.Message = "This is a Test";
var displayform = _objStudentService.DisplayStudentInformation();
return View(displayform);
}
}
Here is my html for the form
#using System.Net.Mime
#model Zakota.University.Domain.DTO.StudentDto
<form action="" method="post">
<div>
<label>First Name</label>
#Html.TextBoxFor(x=>x.FirstName, new { id = "testid1", name="firstname" })
</div>
<div>
<label>Last Name</label>
#Html.TextBoxFor(x=>x.LastName, new { id = "testid1", name="lastname" })
</div>
<div>
<label>Email Address</label>
#Html.TextBoxFor(x=>x.EmailAddress, new { id = "testid1", name="emailaddress" })
</div>
<div>
<label>Department</label>
#Html.DropDownListFor( x=> x.GetAllDepartments,new SelectList(Model.GetAllDepartments,"DepartmentId","DepartmentDescription"), new {#class = "mydropdown", name="dept"})
</div>
<div>
<label></label>
<input type="submit" value="submit"/>
</div>
</form>
I want to be able to get the selected value of the department from dropdownListFor box. I am getting null as the selected value.
Please assist. All other values are correct. The code below is part of the controller code. I just decided to separate it.
[HttpPost]
public ActionResult Index(StudentDto objstudent)
{
string strFirstName = objstudent.FirstName;
string strLastName = objstudent.LastName;
string strEmailAddress = objstudent.EmailAddress;
string strDept = Request.Form["dept"];
var displayform = _objStudentService.DisplayStudentInformation();
return View(displayform);
}
A <select> element postback a single value. You cannot bind a <select> to a collection of complex objects (in your case List<DepartmentDto>). Start by creating a view model representing what you want to edit
public class StudentVM
{
[Display(Name = "First Name")]
[Required(ErrorMessage = "Please enter your first name")]
public string FirstName { get; set; }
.... // other properties of StudentDto
[Display(Name = "Department")]
[Required(ErrorMessage = "Please select a department")]
public int SelectedDepartment { get; set; }
public SelectList DepartmentList { get; set; }
}
Next, your StudentDto model should not contain a property containing a collection of all departments. Your use of DropDownListFor() suggest each student has only one Department, therefore the property should be `public DepartmentDto> Department;
Controller
public ActionResult Create()
{
StudentVM model = new StudentVM();
ConfigureCreateViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(StudentVM model)
{
if(!ModelState.IsValid)
{
ConfigureCreateViewModel(model); // reassign the select list
return View(model); // return the view so user can correct errors
}
StudentDto student = new StudentDto()
{
FirstName = model.FirstName,
LastName = model.LastName,
EmailAddress = mdoel.EmailAddress,
Department = db.Departments.Find(model.SelectedDepartment) // modify to suit
}
// save StudentDto and redirect
}
private void ConfigureCreateViewModel(StudentVM model)
{
List<DepartmentDto> departments = // call a service to get a collection of all departments
model.DepartmentList = new SelectList(departments, "DepartmentId","DepartmentDescription");
}
View
#model yourAssembly.StudentVM
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName)
#Html.ValidationMessageFor(m => m.FirstName)
.... // other controls of StudentVM
#Html.LabelFor(m => m.SelectedDepartment)
#Html.DropDownListFor(m => m.SelectedDepartment, Model.DepartmentList, "--Please select--")
#Html.ValidationMessageFor(m => m.SelectedDepartment)
<input type="submit" value="submit"/>
}
Side notes:
Use #Html.LabelFor(m => m.FirstName) rather than <label>First
Name</label>. A <label> is an element associated with a control
(clicking on it sets focus to the associated control) - you usage
does nothing because it is missing the for attribute
The html helper methods correctly give the elements an id and
name attribute. In you case you are generating invalid html by
creating duplicate id attributes (the first 3 elements have
id="testid1") and you should never attempt to set the name
attribute (in the first 3 cases, your just setting it to what it
already is anyway, but in the case of the dropdown, you trying to
change it to name="dept" which fortunately does not work - because
if it did, binding would fail!)
You should also consider adding validation attributes to your view
model properties, e.g. [Required(ErrorMessage="Please enter a first
name")] and in the view including #Html.ValidationMessageFor(m =>
m.FirstName)
I am encrypting id to hide the raw id in query string and passing it to the controller. But the Id is not retaining back in the postback to the controller.
for eg
/Vendor/EditVendor/mELirpUhRYksFj7k8-XBcQ%3d%3d
DecryptLong() method will decrypt the above id string mELirpUhRYksFj7k8-XBcQ%3d%3d to 1
controller
public ActionResult EditVendor(string id)
{
var vendor = _vendorService.GetVendorById(id.DecryptLong());
return View(vendor);
}
[HttpPost]
public ActionResult EditVendor(Vendor vendor)
{
if (ModelState.IsValid)
{
vendor.Id -- it is always zero and not retaining back
_vendorService.EditVendor(vendor);
}
return View(vendor);
}
In view
#model Eclatech.KidsHub.Objects.Vendor
#{
ViewBag.Title = "EditVendor";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit Vendor</h2>
#using(Html.BeginForm("EditVendor","Vendor",FormMethod.Post, new Dictionary<string, object>
{
{"class","form-horizontal"},
{"role","form"}
}))
{
<div class="form-group">
#Html.LabelFor(m => m.VendorName, new Dictionary<string, object>
{
{"class","col-sm-2 control-label"}
})
<div class="col-sm-10">
#Html.TextBoxFor(m => m.VendorName,new Dictionary<string, object>
{
{"class","form-control"}
})
</div>
</div>
#Html.HiddenFor(m => m.Id)
<input type="submit" class="btn btn-primary btn-default" value="Save" />
}
Model
public class Vendor : AuditableEntity<long>
{
public string VendorName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public abstract class AuditableEntity<T> : Entity<T>, IAuditableEntity
{
[ScaffoldColumn(false)]
public DateTime CreatedDate { get; set; }
[MaxLength(256)]
[ScaffoldColumn(false)]
public string CreatedBy { get; set; }
[ScaffoldColumn(false)]
public DateTime UpdatedDate { get; set; }
[MaxLength(256)]
[ScaffoldColumn(false)]
public string UpdatedBy { get; set; }
}
public abstract class Entity<T> : BaseEntity, IEntity<T>
{
private static long _rowNumber;
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual T Id { get; set; }
[NotMapped]
public virtual long RowNumber
{
get { return ++_rowNumber; }
}
}
The problem is that your parameter name for the EditVendor method is named id and you are returning a model that also has a property named id. When you call the EditVendor method, the value of the parameter is added to ModelState which overrides the value of property Vendor.Id. If you inspect the html generated by #Html.HiddenFor(m => m.Id) you will see that the value of the input is mELirpUhRYksFj7k8-XBcQ%3d%3d, not the value returned by DecryptLong(). When this posts back, it cannot be bound to type int so Id has its default value of zero.
You can test this by adding ModelState.Clear(); before calling GetVendorById(). This will clear the value of Id and the hidden inputs value will now be 1. To solve the problem, change the name of the parameter, for example
public ActionResult EditVendor(string vendorID)
{
var vendor = _vendorService.GetVendorById(vendorID.DecryptLong());
return View(vendor);
}
I'm tryng to be more precise to my previous question which can be found here, I got some nice answers but couldn't figure out how to use it in my situation Previous question
I got some nice answers but couldn't figure out how to use it in my situation.
basically I want to have registration page which contains
Email //Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
UserName//Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
Password//Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
Role//dropdownlist, comes from Role(datamodel) class, also Roles table exists in database
In my controller I have impelmented my Register method in following way:
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();
}
}
in my get method i'm passing the roles to view and right now i'm using AspNetUser as model in View
#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("Login", "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 no how to generate dropdownlist)
<button class="btn btn-large btn-primary" type="submit">Sign In</button>
}
</div>
can u tell me how to get that dropdownlist and how can I pass that selected value to controller to use it so that i can put user in role during registration? Would it be better to create new model for Registration?
Edit: AspNetUser model
public class AspNetUser
{
private ICollection<Role> _roles= new Collection<Role>();
public Guid Id { get; set; }
[Required]
public virtual String Username { get; set; }
public virtual String Email { get; set; }
[Required, DataType(DataType.Password)]
public virtual String Password { get; set; }
public virtual String FirstName { get; set; }
public virtual String LastName { get; set; }
[DataType(DataType.MultilineText)]
public virtual String Comment { get; set; }
public virtual Boolean IsApproved { get; set; }
public virtual int PasswordFailuresSinceLastSuccess { get; set; }
public virtual DateTime? LastPasswordFailureDate { get; set; }
public virtual DateTime? LastActivityDate { get; set; }
public virtual DateTime? LastLockoutDate { get; set; }
public virtual DateTime? LastLoginDate { get; set; }
public virtual String ConfirmationToken { get; set; }
public virtual DateTime? CreateDate { get; set; }
public virtual Boolean IsLockedOut { get; set; }
public virtual DateTime? LastPasswordChangedDate { get; set; }
public virtual String PasswordVerificationToken { get; set; }
public virtual DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public virtual ICollection<Role> Roles
{
get { return _roles; }
set { _roles = value; }
}
}
You'd better have a view model specifically designed for this view. Think of what information you need in the view and define your view model:
public class RegisterViewModel
{
public string Email { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string SelectedRole { get; set; }
public IEnumerable<SelectListItem> Roles { get; set; }
}
As you can see from this view model, in order to have a dropdown list you need 2 properties: one scalar property that will hold the selected value and one collection property to hold the list of available values.
and then:
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
var model = new RegisterViewModel();
model.Roles = roles.Select(r => new SelectListItem
{
Value = r,
Text = r,
});
return View(model);
}
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
// the model.SelectedRole will contain the selected value from the dropdown
// here you could perform the necessary operations in order to create your user
// based on the information stored in the view model that is passed
// NOTE: the model.Roles property will always be null because in HTML,
// a <select> element is only sending the selected value and not the entire list.
// So if you intend to redisplay the same view here instead of redirecting
// makes sure you populate this Roles collection property the same way we did
// in the GET action
return Content("Thanks for registering");
}
and finally the corresponding view:
#model RegisterViewModel
#{
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("Login", "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(model => model.SelectedRole, Model.Roles)
</div>
<button class="btn btn-large btn-primary" type="submit">Sign In</button>
}
</div>
I have these two ViewModels
public class AboutViewModel : ViewModel
{
public override long Id { get; set; }
public override string PageTitle { get; set; }
public override string TitleDescription { get; set; }
public override string ContentTitle { get; set; }
public virtual AboutItemViewModel AboutItem { get; set; }
}
public class AboutItemViewModel
{
public long Id { get; set; }
[AllowHtml]
public string Content { get; set; }
public string ImageUrl { get; set; }
public HttpPostedFileBase FileToUpload { get; set; }
}
Here is my controller:
[ValidateInput(false)]
[ValidateAntiForgeryToken, HttpPost]
public ActionResult Create(long? siteid, long? cid, AboutViewModel model)
{
return View(model);
}
Here is my View:
#using (Html.BeginForm("Create", "About", new { siteid = ViewData["siteid"], cid = ViewData["cid"] },FormMethod.Post,new { enctype = "multipart/form-data", #class = "form-horizontal rtl", autocomplete = "off" }))
{
<div class="controls">
<input type="file" name="FileToUpload" id="FileToUpload" style="margin-right: -9px;">
</div>
<div class="controls">
#Html.ValidationMessageFor(o => o.AboutItem.FileToUpload, "", new { id = "spanfile", #class = "alert alert-block alert-error span3 pull-right", style = "margin-right: 160px;" })
</div>
<div class="control-group pull-left">
<button type="submit" class="btn btn-large" data-toggle="button">Save</button>
</div>
}
How to bind the file to FileToUpload to stop returning me a null?
Except:
If I put it in the main AboutViewModel than it's returns a correct value.
Since the FileToUpload property is in the AboutItem proprety, which is a class property of the parent ViewModel, you need to preface the name of your input element with the property it came from. That's a long way of saying that the name of your file input should be AboutItem.FileToUpload.
<input type="file" name="AboutItem.FileToUpload" id="AboutItem_FileToUpload" />
This should take care of the model binding for you. Additionally, you can test this by using an HTML helper on on of the other properties of the AboutItem class. For instance:
#Html.TextBoxFor(x=>x.AboutItem.Id)
This should render in the HTML
<input type="text" name="AboutItem.Id" id="AboutItem_Id />
EDIT
Apparently the id attribute will be rendered with an underscore instead of a dot. However, since the ID attribute is not used in model binding, it shouldn't really matter.