Using input tag helper in asp.net core - the "value" is empty - asp.net-core

I am brand new on .net 5 asp tag helpers. I have models like these:
public class MyForm
{
public String Url { get; set; }
...
}
public class MyViewModel
{
[Required]
public String Url { get; set; }
[DisplayName("Seller username")]
[Required]
public String SellerName { get; set; }
...
}
In my controller I got and URL value for example https://myshop.com/item?id=1234
public IActionResult AddLinkWizardSecond(MyForm form)
{
var model = new MyViewModel() {
Url = "https://anotherShop.com/index.html",
SellerName = "test user 123",
};
return PartialView("myView", model);
}
Where my view looks as:
#model MyViewModel
<input asp-for="Url" readonly>
<input asp-for="SellerName">
It is said that the tag helper for input element renders all necessary tags including value tag also. Several examples (on the internet) shows that the rendered html contains <input value="...somevalue...". But - for me this is odd - the rendered html I got in my browser looks as:
<input name="Url" id="Url" value="https://myshop.com/item?id=1234" ... />
<input name="SellerName" id="SellerName" value="" ... />
There must be reason behind this - but I cant catch it. Could somebody give me some ideas why the url contains the posted data instead of the new one, and why the seller name value is empty when I fill these properties? I tried to put the values to ViewBag and ViewData before - but none of them are working:
...
this.ViewData["SellerName"] = model.SellerName;
return PartialView("myView", model);
Is this too much I ask for the tag helpers? They cannot use the current values? Then where the posted value comes from?
Apologizes for the dummy question :( Any advice is greatly welcome which can help me out from the deep swamp of despair where I am now :(

You are correct this is a design choice in the framework. You can read about why it was made, some theory, and also a few work arounds in this blog post.
For example, calling ModelState.Clear(); in your Post action will display the behavior you are looking for.
However, its standard "practice" to use the Post Redirect Pattern regardless, which solves the problem.

Related

How to bind dynamic complex objects created using partial-view to a collection property in view-model

I'm unable to bind a collection of child-complext objects created dynamically using a partial-view to view-model IEnumerable property.
I have successfully bound objects created dynamically using partial-views to a view-model using a technique I found on this blog https://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/. I have followed the same technique but I'm unable to bind a collection to a IEnumerable property in a view-model.
[BindRequired]
public class EmployeeViewModel
{
other properties....
public IEnumerable<ContactDetailViewModel> EmployeeContact { get; set; }
}
[BindRequired]
public class ContactDetailViewModel
{
// I use this as my indexer for dynamic elements
public string RecordId { get; set; } = Guid.NewGuid().ToString();
public string Telephone { get; set; }
public string EmailAddress { get; set; }
public string ContactDescription { get; set; }
}
I call into this action-method via ajax to add dynamic contact detail elements and it returns the partial-view as html and it works fine.
[Route("[action]", Name = "BlankEmployeeContactDetail"), HttpGet("AddBlankContactDetail")]
public PartialViewResult AddBlankContactDetail()
{
return PartialView("_ContactInformation", new ContactDetailViewModel());
}
The initial contact detail is added to the main-view using the following, kindly follow this link https://1drv.ms/u/s!AkRSHVUtFlKhuHaxH96Ik4ineATE to download the main view and partial-view cshtml files. It is also noteworthy to mention that model binding fails for all other properties when I include this partial-view but works when I comment it out. I'm baffled and would greatly appreciate any help you can afford me.
<section id="widget-grid" class="">
<div class="row contactContainer">
#{ await Html.RenderPartialAsync("_ContactInformation", new ContactDetailViewModel()); }
</div>
</section>
This is the controller action method I'm trying to bind posted data to:
[Route("[action]"), HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public IActionResult Register([FromForm] EmployeeViewModel model, [FromQuery] string returnUrl = null)
{
if (ModelState.IsValid)
{
}
return View(model);
}
In order to bind, the input names much follow a particular convention that maps to what you're binding to. While it's unclear from your question, my best guess is that you're trying to ultimately bind to an instance of EmployeeViewModel, which means that your contact information inputs would need names like: EmployeeContact[0].Telephone, but when you pass an instance of ContactDetailViewModel along as the "model" of the partial view, the names will be just Telephone, and worse, these same names will be repeated over and over, i.e. each contact information set of fields you create will all have an input named just Telephone.
Long and short, you need the context of the whole model to generate the correct input names. You have a couple of options.
Since you're retrieving the set of fields via an AJAX request, it would be possible to pass the "prefix" to use along with that request. In other words, you can keep track of an index value, counting how many of these sections you've added, and then send along with the request for a new section something like
prefix: 'EmployeeContact[' + (i + 1) + ']',
Then, in your partial view:
#{ await Html.RenderPartialAsync("_ContactInformation", new ContactDetailViewModel(), new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = ViewBag.Prefix } } ); }
That's a little hacky, and honestly probably rather prone to error, though. The better option would be to take an entirely different approach. Instead of calling back to get the partial view, define it just once as a template:
<script type="text/html" id="ContactInformationTemplate">
<!-- HTML for contact information inputs -->
</script>
Then, using a library like Vue, React, Angular, etc., you can set up a "foreach" construction tied to a particular JavaScript array which uses this template to render items in that array. Then, adding a new set of inputs is as simple as adding a new item to the array. You will have to do some works to customize the input names based on the index of the item in the array, but all of these client-side frameworks have ways to do that. That would also have the side benefit of not having to make an AJAX request every time you want to add a new section.

Intellisense for asp-controller or asp-action in ASP.NET Core MVC

I have just installed latest version of VS 2017 with asp.net core 2. The tag helpers intellisense works for asp-for="FirstName" (where FirstName is a property of my Model), but it does not work for something like asp-controller ="..." or asp-action="...".
Is there any way (for example an Extension) to provide intellisense for controller/action... names to avoid manual typing of them?
Update (More info):
for example, if you have a model named Customer as:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Code { get; set; }
}
and following razor code in your Index.cshtml:
#{ var myVar = "abc";}
<input type="text" asp-for="#myVar" />
<label asp-for="FirstName">Name:</label>
<input type="text" asp-for="FirstName" />
<a asp-controller="Home" asp-action="About">About</a>
you will have intellisense for FirstName in asp-for="Firs...", but not for similar properties asp-controller="Hom... or asp-action="Abou..." while both of them are purple and bold (as TagHelpers).
It is not possible, essentially because finding controllers - and, therefore, actions - is tricky. A controller can be a lot of things:
A class that inherits from BaseController
A POCO class with the Controller suffix
Other supplied by some conventions
Other defined at runtime

MVC 4 Validation Attribute is not working for dynamically added fields

Here are my Product and ProductItem classes/models:
public class Product
{
public int ProductId { get; set; }
[Required(ErrorMessage="Enter Name")]
public string Name { get; set; }
public List<ProductItem> productitems { get; set; }
[Required(ErrorMessage="Enter Price")]
public decimal Price { get; set; }
}
public class ProductItem
{
[Required(ErrorMessage="Select Raw Material")]
public int RawMaterial { get; set; }
[Required(ErrorMessage="Enter Quantity")]
public decimal Qty { get; set; }
}
For ProductItem I am adding its fields dynamically with jQuery, as you can see here:
$("#btnAddProductItem").click(function () {
$.getJSON("/rawmaterial/GetRawMaterials", null, function (data) {
var productItem = $("<tr class='productItem' id='productItem-0'><td><select id='rmlist-0' name='productitems[0].RawMaterial'></select><span class='field-validation-valid' data-valmsg-for='productitems[0].RawMaterial' data-valmsg-replace='true'></span></td><td><input type='text' id='rmqty-0' name='productitems[0].Qty'/><span class='field-validation-valid' data-valmsg-for='productitems[0].Qty' data-valmsg-replace='true'></span></td></tr>");
$("#productItem").append(productItem);
$("#rmlist-0").addItems(data);
});
});
Now the validation attributes applied on Name and Price are working fine but not on the fields added dynamically (i.e. "RawMaterial" and "Qty").
Please give me the suggestions how this validation will work ?
Note: For testing purpose I have just added the first object of the List indexed with 0.
There are several ways to accomplish this -
PARTIAL VIEW: Since you are using Server Side data annotation as I see from the class definitions, then it is not a good idea to load dynamically with js. Because you will miss out all the validation that MVC 4 could have created automatically. So, the best solution I would suggest is taking the code that you are adding dynamically to a partial view file and then get the html with ajax call and then populating the HTML.
JS VALIDATION: But, if it is a must that you should use JS, then you have to add all the validation items yourself. To do that you have to do some extra works -
First, inspect the HTML with any developer tools, you will notice that there is a <span> attribute appended after each item to show the error which has a target mentioned. You have to append similar attributes to your elements
With MVC 4 unobtrusive validation, all the validation attributes and rules are added with the target element with data attributes. Each one is based one the validation they stands for. You have you create attributes similar to that.
Finally, after adding all the validation items in JS, reset the form so that it parses the new validations added and work accordingly. The code to parse the validations are here -
var form = $("form") //use more specific selector if you like
form.removeData("validator").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse(form);
But I would prefer the partial view solution, since it will require least amount of re-work and also gives you option to keep all your validation in one place. You don't have to worry about new validations to be ported to js in future.

MVC ViewModel with editable and readonly data best practice

I'm wondering what is the best practice for dealing with editable/read only field in the same viewModel.
I'm facing this problem for a bigger ViewModel but let's assume I have a very simple ViewModel:
public class BaseListViewModel
{
public int Id { get; set; }
public bool IsCheckedForAction { get; set; }
public string DisplayName { get; set; }
}
My PartialView:
#model Wims.Website.ViewModels.Shared.BaseModelListViewModel
#using Wims.Website.Classes.Helpers.ExtentionMethods
<div class="dataDetail">
<div>
<div class="float-left">
#Html.CheckBoxFor(model => model.IsCheckedForAction)
</div>
<div class="float-left">
#Html.LabelFor(model => model.IsCheckedForAction, Model.DisplayName)
#Html.GenerateSecureDataControls(w => w.Id)
</div>
</div>
</div>
<div style="clear: both"></div>
Obviously, when i Post my data, DisplayName will not be filled.
Let's assume some validation fails, and I just return the data i recieved the DisplayName would be missing.
[HttpPost]
public ActionResult Edit(BaseListViewModel stuff)
{
if (ModelState.IsValid)
{
...
return View("Index");
}
return View(stuff);
}
I know there is several way to fix this:
1) add
#Html.HiddenFor(model => model.DisplayName)
in the view, which is ok if it's just 1 field, but, what happens if i do have 10 display only field?
2) requery data if (!Model.isValid) in [HttpPost].
3) I guess I could cache it in TempData ?
What is the best way to go for this?
Thanks!
Edit: I am trying to avoid having to requery the data if validation fails
I'd use the PRG pattern. It is more DRY as you only build ViewModel in the GET action. If validation fails then redirect to GET and get the model state out of tempdata.
The attributes on from this article, http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html, or from MVC Contrib https://github.com/mvccontrib/MvcContrib/blob/master/src/MVCContrib/Filters/ModelStateToTempDataAttribute.cs, make it easy to pass the Modelstate between the POST and the GET
The POST action should perform the same initialisation of the viewmodel as the GET action. You could move the initialisation code into a common private function in the controller. The reason for this is if validation fails because of some concurrent change to the same data, the validation errors would be displayed to the user along with the new data. You could use the PRG pattern as well if the views allow for it.

MVC-3 and Dynamic - #Html.Label(View.X) not Rendering

Using MVC-3, Razor:
-- MyController --
public ActionResult Index(String message) // where message = "hello"
{
ViewModel.Test1 = "This is a test";
ViewModel.Test2 = "This is another test. " + message;
}
-- Index.cshtml --
#Html.Label((string)View.Test1)
<br />
#Html.Label((string)View.Test2)
Why will it only render out the following?
<label for="This is a test">This is a test</label>
<br />
It's been driving me absolutely crazy over the past few days and seems to make no sense. There has to be a reason for it.
I can debug this and step through thew view. In the view, I watch as this line is processed and the value of View.Test2 is "This is another test. hello".
I have cases where I am doing the following and it works fine.
(ex)
ViewModel.Something = this.readDataService.GetSomething();
What's the difference?
Thanks,
Rob
Looks like you are using a pre-RC2 version of ASP.NET MVC 3. ViewModel was changed to ViewBag in RC 2 (see the this post by Scott Guthrie).
With earlier previews of ASP.NET MVC 3 we exposed this API using a dynamic property called “ViewModel” on the Controller base class, and with a dynamic property called “View” within view templates. A lot of people found the fact that there were two different names confusing, and several also said that using the name ViewModel was confusing in this context – since often you create strongly-typed ViewModel classes in ASP.NET MVC, and they do not use this API.
With RC2 we are exposing a dynamic property that has the same name – ViewBag – within both Controllers and Views.
And it does look like you are trying to use ViewModel as the strongly typed model for your view. Instead, create a class to use as your model and then use #Html.LabelFor:
public class PersonModel
{
public string Name { get; set; }
}
in the controller:
PersonModel model = new PersonModel { Name = "John" };
return View(model);
in the view:
#Html.LabelFor(model => model.Name): #Html.TextBoxFor(model => model.Name)
which renders:
<label for="Name">Name</label>: <input id="Name" name="Name" type="text" value="John" />
HTH