Create Custom Editor in ASP.NET Core - asp.net

I am in need of some advice. I am wanting to implement my own custom editor for a specific object type, Address. I started by reading the documentation for Tag Helpers on the .NET Core website and then read about View Components on the Core site and neither really made since for my exact scenario.
I have a model Address:
public class Address
{
public Guid Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string StateCode { get; set; }
public string PostalCode { get; set; }
}
I want to create either a custom editor template, tag helper, or view component that will allow me to do something like this (In my "Edit" View):
#model TestApplication.Models.Customer
<h2>Edit Customer</h2>
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Edit Customer</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Name" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
<address-editor asp-for="HomeAddress" />
<address-editor asp-for="WorkAddress" />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</form>
I would like the HTML to be rendered like so:
Edit Customer
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Edit Customer</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Name" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="Name" name="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
<h4>Home Address</h4>
<input type="hidden" asp-for="HomeAddress_Id" />
<div class="form-group">
<label asp-for="HomeAddress_AddressLine1" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="HomeAddress_AddressLine1" name="HomeAddress_AddressLine1" class="form-control" />
</div>
</div>
<div class="form-group">
<label asp-for="HomeAddress_AddressLine2" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="HomeAddress_AddressLine2" name="HomeAddress_AddressLine2" class="form-control" />
</div>
</div>
<div class="form-group">
<label asp-for="HomeAddress_City" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="HomeAddress_City" name="HomeAddress_City" class="form-control" />
</div>
</div>
<div class="form-group">
<label asp-for="HomeAddress_StateCode" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="HomeAddress_StateCode" name="HomeAddress_StateCode" class="form-control" />
</div>
</div>
<div class="form-group">
<label asp-for="HomeAddress_PostalCode" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="HomeAddress_PostalCode" name="HomeAddress_PostalCode" class="form-control" />
</div>
</div>
<h4>Work Address</h4>
<input type="hidden" asp-for="WorkAddress_Id" />
<div class="form-group">
<label asp-for="WorkAddress_AddressLine1" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="WorkAddress_AddressLine1" name="WorkAddress_AddressLine1" class="form-control" />
</div>
</div>
<div class="form-group">
<label asp-for="WorkAddress_AddressLine2" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="WorkAddress_AddressLine2" name="WorkAddress_AddressLine2" class="form-control" />
</div>
</div>
<div class="form-group">
<label asp-for="WorkAddress_City" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="WorkAddress_City" name="WorkAddress_City" class="form-control" />
</div>
</div>
<div class="form-group">
<label asp-for="WorkAddress_StateCode" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="WorkAddress_StateCode" name="WorkAddress_StateCode" class="form-control" />
</div>
</div>
<div class="form-group">
<label asp-for="WorkAddress_PostalCode" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input id="WorkAddress_PostalCode" name="WorkAddress_PostalCode" class="form-control" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</form>
Am I on the right direction by looking into Editor Templates? If so, how do I prefix the control names with the Model.PropertyName (i.e. HomeAddress_)?

I believe my answer came about by researching view components a bit more. I could have gone the direction of a tag helper, view component, or editor template. I decided on view component and needed to know how to pass the entire model information into the view component, well, this question helped me get there. The issue I was having (or question) was that I needed to know about ModelExpression. That class contains everything that I need (name and model), so I was able to do like the following:
<vc:address-editor asp-for="#Model.HomeAddress"></vc:address-editor>
Then, in my AddressEditor ViewComponent, I simply used the two properties that I needed:
public IViewComponentResult Invoke(ModelExpression aspFor)
{
ViewBag.AspFor = aspFor.Name;
return View(aspFor.Model);
}

Related

how to pass data from bootstrap form to mockemployeerepo in asp .net core razor pages

<div class="form-group row">
<label asp-for="EmployeeEditProperty.Name" class="col-sm-2 col-form-label">
</label>
<div class="col-sm-10">
<input asp-for="EmployeeEditProperty.Name" class="form-control" placeholder="Name">
</div>
</div>
<div class="form-group row">
<label asp-for="EmployeeEditProperty.Email" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input asp-for="EmployeeEditProperty.Email" class="form-control" placeholder="Email">
</div>
</div>
Data not passing to
Employee employee= _EmployeeList.FirstOrDefault(e => e.ID == UpdatedEmployee.ID);
if(employee !=null)
{
employee.Name = UpdatedEmployee.Name;
employee.Email = UpdatedEmployee.Email;
employee.Department = UpdatedEmployee.Department;
}
The properties in view should match the property name in your post action,
You can check my demo:
<form method="post">
<div class="form-group row">
<input hidden asp-for="EmployeeEditProperty.ID" value="1" />
</div>
<div class="form-group row">
<label asp-for="EmployeeEditProperty.Name" class="col-sm-2 col-form-label">
</label>
<div class="col-sm-10">
<input asp-for="EmployeeEditProperty.Name" class="form-control" placeholder="Name">
</div>
</div>
//....
<input type="submit" value="Submit" />
The property names EmployeeEditProperty, so in post method, it should be EmployeeEditProperty too:
public ******(Employee EmployeeEditProperty)
Result:
Model Binding in razor pages, you can check this article

How to move data from one action to another action in the same controller for forms?

How do I move data from an Iactionresult to another action result? I have been trying to display the data from the form and view it another Iactionresult? I attempt to use Tempdata but it seems like there is an error. Could anyone help me with it?
This action displays an individual product details when I click on an particular Id.
[HttpGet]
public IActionResult Details(int id)
{
string sql = String.Format(#"SELECT * FROM WBProduct
WHERE Id = {0}", id);
List<Product> lstProduct = DBUtl.GetList<Product>(sql);
if (lstProduct.Count == 0)
{
TempData["Message"] = $"Product #{id} not found";
TempData["MsgType"] = "warning";
return RedirectToAction("Index");
}
else
{
Product cdd = lstProduct[0];
return View(cdd);
}
}
I would like to display the the details of the product in this IActionResult
[HttpPost]
public IActionResult Create()
{
return View("Create");
}
View for Details:
#model Product
<div>
<div class="form-group row">
<div class="offset-sm-2"><h2>#Model.ProductName</h2></div>
</div>
<div class="form-group row">
<div class="offset-sm-2 col-sm-5">
<img id="ImgPhoto" src="~/images/product/#Model.ProductImage" style="width:400px;" />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="City">Weight: </label>
<div class="col-sm-5">
<input type="text" asp-for="ProductWeight" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="Date">Stock :</label>
<div class="col-sm-5">
<input type="text" asp-for="ProductStock" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="Cost">Price: </label>
<div class="col-sm-5">
<input type="text" asp-for="ProductPrice" asp-format="{0:C}" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="Story">Description: </label>
<div class="col-sm-5">
<textarea asp-for="ProductDescription" rows="8" cols="20" class="form-control" readonly></textarea>
</div>
</div>
<div class="form-group row">
<a href="http://localhost:50528/Product/Create" class="btn btn-info" role="button" > Add to Cart </a>
</div>
</div>
Create View:
#model Product
<div class="form-group row">
<div class="offset-sm-2"><h2>#Model.ProductName</h2></div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="City">Weight: </label>
<div class="col-sm-5">
<input type="text" asp-for="ProductWeight" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="Date">Stock :</label>
<div class="col-sm-5">
<input type="text" asp-for="ProductStock" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="Cost">Price: </label>
<div class="col-sm-5">
<input type="text" asp-for="ProductPrice" asp-format="{0:C}" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="Story">Description: </label>
<div class="col-sm-5">
<textarea asp-for="ProductDescription" rows="8" cols="20" class="form-control" readonly></textarea>
</div>
</div>
The error message that I got was:
This should be a GET action, not a POST one, then you should extract the info from the TempData and pass it as parameter to the view cshtml.
TempData["Product"] = JsonConvert.SerializeObject(lstProduct[0]);
return RedirectToAction("Create");
Now you can deserialize in the Create action and retrieve your Product
[HttpGet]
public IActionResult Create()
{
// If the caller has prepared a product we can show it.
if(TempData.ContainsKey("Product"))
{
Product p = JsonConvert.DeserializeObject<Product>(TempData["Product"]);
return View(p);
}
else
return View();
}
If you want to move data from one action to another action in the same controller
just call one action from another and put data as an input parameter of another action.
To send message to Index action, at first create a class for the message:
public class ErrorMsg
{
public string Message {get; set;}
public string MessageType {get; set;}
}
Change your action Index to this:
public IActionResult Index(ErrorMsg errorMsg)
{
// if action called from another controller action, details for exapmple,
//errorMsg will contain data from that action
// otherwise errMsg will be an empty default object with empty strings
//Check if error
if(!string.IsNullOrEmpty(errorMsg.Message) ...your error code
else ....your index code here
}
Change your action details code:
public IActionResult Details(int id)
{
string sql = String.Format(#"SELECT * FROM WBProduct
WHERE Id = {0}", id);
List<Product> lstProduct = DBUtl.GetList<Product>(sql);
if (lstProduct.Count == 0)
{
var errMsg = new ErrMessage {
Message = $"Product #{id} not found",
MessageType = "warning"
}
return Index(errMsg);
}
else
{
Product cdd= lstProduct.FirstOrDefault();
//Or you can try again var cdd = lstProduct[0]; if you like it more
return View("Details", cdd);
}
}
Change your create action to this:
public IActionResult Create(Product product)
{
// if action called from another controller action, "product" will contain data //from that action
// otherwise "product" will be posted from the view or it will be an empty model with the default value fields
if(product.Id ==0) ... call add ef code
else ... call update ef code
}
And you have to add <form tag to all your views, othewise if will not post back any data, and add Product.Id hidden field inside of form:
#model Product
#using (Html.BeginForm("Create", "Product", FormMethod.Post)
{
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div>
<input type="hidden" asp-for="#Model.Id" />
<div class="form-group row">
<div class="offset-sm-2"><h2>#Model.ProductName</h2></div>
</div>
<div class="form-group row">
<div class="offset-sm-2 col-sm-5">
<img id="ImgPhoto" src="~/images/product/#Model.ProductImage" style="width:400px;" />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="City">Weight: </label>
<div class="col-sm-5">
<input type="text" asp-for="ProductWeight" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="ProductStock">Stock :</label>
<div class="col-sm-5">
<input type="text" asp-for="ProductStock" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="ProductPrice">Price: </label>
<div class="col-sm-5">
<input type="text" asp-for="ProductPrice" asp-format="{0:C}" class="form-control" readonly />
</div>
</div>
<div class="form-group row">
<label class="control-label col-sm-2" for="ProductDescription">Description: </label>
<div class="col-sm-5">
<textarea asp-for="ProductDescription" rows="8" cols="20" class="form-control" readonly></textarea>
</div>
</div>
<div class="form-group row">
<button class="btn btn-info btn-link" type="submit"> Add to Cart </button>
</div>
</div>
}

ASP.NET Core POST method always pending

So, I'm trying to create a controller for an existing table in my database.
This is my Create method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(ArticleCategoryViewModel model)
{
ArticleCategory cat = new ArticleCategory { Code = model.Code, Name = model.Name, Description = model.Description, VATId = model.VATId };
if (ModelState.IsValid)
{
_context.ArticleCategories.Add(cat);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewData["VATId"] = new SelectList(_context.VATs, "Id", "Percentage");
return View(model);
}
This is the form from cshtml file for create:
<form asp-action="Create" class="form-horizontal bordered-group" role="form">
<div class="form-horizontal">
<h4>ArticleCategoryViewModel</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Code" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Code" class="form-control" />
<span asp-validation-for="Code" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Description" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Name" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="VATId" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select asp-for="VATId" class ="form-control" asp-items="#((IEnumerable<SelectListItem>)ViewData["VATId"])"></select>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default submit" />
</div>
</div>
</div>
</form>
Getting the information from database works, like get for index and details and edit, but when i try to post something like on create or after editing and i click submit button, its just pending. I tried every solution that i could find on the web but nothing works and i have no more ideas.
No logs are written, no error is thrown, nothing happens and i dont know why.
If i put a breakpoint inside the create method, it will not hit, so no debug.
Any ideas ?

How to change text input to select input in chtml?

I have a text form
<form asp-action="Create">
<div class="form-horizontal">
<h4>Flight</h4>
<hr />
<div asp-validation-summary="ValidationSummary.ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="FlyDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="FlyDate" class="form-control" />
<span asp-validation-for="FlyDate" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="FlyFrom" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="FlyFrom" class="form-control" />
<span asp-validation-for="FlyFrom" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="FlyTo" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="FlyTo" class="form-control" />
<span asp-validation-for="FlyTo" class="text-danger" />
</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>
</form>
I want to change FlyFrom and FlyTo to select options where users can select cites name (Tokyo, Osaka...). Currently they are text field. How can I do it?
There are two ways to populate based on your requirement.
If values are constant you can directly hard code two drop downs.
<select>
<option>State1</option>
<option>State1</option>
</select>
if values are coming from DB you can fill those to values ViewBag object on index method
ViewBag.FromState=<<your list of states>>
ViewBag.ToDate==<your list of states>>
.cshtml
#Html.DropDownList("FromState",ViewBag.FromStaes)

Thymeleaf checkbox not passing value to controller

<form class="form-horizontal" action="#" data-th-action="#{/admin/role/permission/save}" data-th-object="${permission}" method="post">
<div class="form-group">
<label class="col-sm-5 control-label" data-th-text="#{permission.list.permission.label}">Permission</label>
<div class="col-sm-7">
<input type="text" hidden="hidden" data-th-value="*{id}" data-th-field="*{id}" ></input>
<input type="text" class="form-control" data-th-value="*{permissionname}" data-th-field="*{permissionname}" ></input>
</div>
</div>
<div class="form-group" th:each="role : ${allRoles} ">
<label class="col-sm-5 control-label" data-th-text="${role.rolename}">Role 1</label>
<div class="col-sm-7">
<input type="checkbox" th:field="*{permRoles}" th:value="${role}"></input>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-5 col-sm-7" >
<button type="submit" class="btn btn-primary" name="action"
data-th-value="#{button.action.save}" data-th-text="#{button.label.save}" >Save</button>
<button type="submit" class="btn btn-default active" name="action"
data-th-value="#{button.action.cancel}" data-th-text="#{button.label.cancel}">Cancel</button>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-5 col-sm-7" >
<p class="text-danger" data-th-if="${#fields.hasErrors('permissionname')}"
data-th-errors="*{permissionname}">type error</p>
</div>
</div>
</form>enter code here
above is the html code , I have a permission object, and wants to assign list of roles to it by using the checkbox, the pass the object back to controller. But the value is not assigned to permission.permroles.
#RequestMapping(value = "/save", method = RequestMethod.POST)
#PreAuthorize("hasRole('PERMISSION_SAVEORADD')")
public ModelAndView savePermission(#ModelAttribute Permission permission, BindingResult result)
throws PermissionNotFoundException {
System.out.println(permission.getPermRoles().size());
permissionDao.updatePermission(permission);
return new ModelAndView("redirect:/admin/role/permission/list");
}
The above is my controller
please help, i am stuck for days.
thank you in advance
I think you could try to solve your problem using hidden input with underscore prefix.
<form th:action="#{${flowExecutionUrl}(_eventId='change')}" th:method="post">
<input type="hidden" name="_fooBar" th:value="${fooBar}"/>
<input type="checkbox" id="fooBar" name="fooBar" th:checked="${fooBar}" />
<!-- rest of code ommited -->
</form>
I had simmilar issue, see my blog post http://lukasgrygar.com/thymeleaf/thymeleaf-tips-and-tricks/#fix-of-problem-with-unchecked-checbox-when-using-thymeleaf-and-spring-web-flow

Resources