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
Related
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.
Let's say I have the following input tag which utilizes the built-in tag helper:
#model ProductViewModel
<label asp-for="Product.Id"></label>
In my case, this expands into the following:
<label for="Product_Id">Id</label>
I see that asp-for is expecting a ModelExpression:
In tag helper implementations, I often see a property like the following:
public ModelExpression For { get; set; }
It appears that this is automatically populated when the tag helper is used.
Is there a way to instantiate a ModelExpression directly in C#?
I.e. something like this:
var exp = new ModelExpression("Product.Id",...)
I'd like to be able to generate "Product_Id" and "Id" from Product.Id as the input tag helper did.
As far as I know, you can specify that your property is to be set to the name of some property on the View's Model object by declaring your property with the ModelExpression type. This will enable any developer using your property to get IntelliSense support for entering a property name from the Model object. More importantly, your code will be passed the value of that property through the ModelExpression's Model property.
Sample code as below:
[HtmlTargetElement("employee-details")]
public class EmployeeDetailTagHelper : TagHelper
{
[HtmlAttributeName("for-name")]
public ModelExpression EmployeeName { get; set; }
[HtmlAttributeName("for-designation")]
public ModelExpression Designation { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "EmployeeDetails";
output.TagMode = TagMode.StartTagAndEndTag;
var sb = new StringBuilder();
sb.AppendFormat("<span>Name: {0}</span> <br/>", this.EmployeeName.Model);
sb.AppendFormat("<span>Designation: {0}</span>", this.Designation.Model);
output.PreContent.SetHtmlContent(sb.ToString());
}
}
Code in the View page:
#model WebApplication7.Models.EmployeeViewModel
<div class="row">
<employee-details for-name="Name" for-designation="Designation"></employee-details>
</div>
Code in the Model
public class EmployeeViewModel
{
public string Name { get; set; }
public string Designation { get; set; }
}
From above code, you can see that we could custom the attribute name. More detail information about using the ModelExpression, check the following links:
Creating Custom Tag Helpers With ASP.NET Core MVC
Expression names
I'd like to be able to generate "Product_Id" and "Id" from Product.Id
as the input tag helper did.
Besides, do you mean you want to change the Product. Id to Product_Id, in my opinion, I'm not suggesting you change it, because generally we can use "_" as a separator in the property name. So, if we are using Product.Id, it means the Product's Id property, and the Product_Id means there have a Product_Id property.
To answer the question:
Is there a way to instantiate a ModelExpression directly in C#"
Yes you can, through IModelExpressionProvider and its CreateModelExpression method. You can get an instance of this interface through DI.
Now, if you're already in your view and working with tag helpers, Zhi Lv's answer is all you need, as the functionality is built-in and much easier to use. You only need IModelExpressionProvider for when you're in your Razor Page, Controller, or perhaps some custom middleware. Personally, I find this functionality useful for my Ajax handlers that need to return one of my ViewComponents that has a ModelExpression argument (so that I can easily call it from my Pages/Views too.)
To call CreateModelExpression, you'll need a strongly-typed instance of ViewData. In Razor Pages, this is as easy as casting the ViewData property to the strongly-typed instance of your PageModel's type (presuming you don't have a page model hierarchy):
var viewData = (ViewDataDictionary<IndexModel>)ViewData;
If you're using MVC and you're in the controller, that won't exist yet. Best you can do is make your own instance.
var viewData = new ViewDataDictionary<ErrorViewModel>(new EmptyModelMetadataProvider(),
new ModelStateDictionary());
Once you get your strongly-typed ViewData instance, you can obtain your desired ModelExpression like this, just like using a lambda expression in your views:
var myPropertyEx = _modelExpressionProvider.CreateModelExpression(viewData,
m => m.MyProperty);
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.
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
I have two projects, d2admin and PartyWeb.
d2admin is the actual UI, it will have all necessary css, js and views etc., and also controllers if required.
PartyWeb is having controllers for each table in Party.
Say I have a table called - Organization. This table's controller will be in PartyWe/Controllers folder.
I will have the views in d2admin.
Now my problem is how can I invoke the OrganizationController exists in PartyWeb from the view Organization.cshtml exists in d2admin?
I tried with Html.RenderAction, this is working for the controllers exists in same, when I call the controller of diff project I am getting - missing method exception.
I found your problem interesting and decided to test for myself. I created two MVC projects (but one of them could be a class library as well, I was lazy though). The first MVC project became the main one with routes and views, the second project got the model and the controller. It worked like a charm from start and here is how I did it.
I created the model in the second project, named Car in my example (the name UsersContext is left from the default files because I wanted to change as little as possible).
namespace PartyBiz.Models
{
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<Car> Cars { get; set; }
}
[Table("Cars")]
public class Car
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int CarId { get; set; }
public string CarName { get; set; }
}
}
I then built the project and created a controller with EF connections to Car (by right clicking on the Controller folder and select MVC controller with read/write actions and views, using Entity Framework)
The controller looked like this when done (many lines have been removed to keep the example short)
namespace PartyBiz.Controllers
{
public class CarController : Controller
{
// UsersContext is a left over from the default MVC project
private UsersContext db = new UsersContext();
public ActionResult Index()
{
return View(db.Cars.ToList());
}
// Many other actions follows here...
}
}
The views that were created in the second project (PartyBiz) I copied over to the first project (d2admin) by drag and drop. I then deleted the views from the second project to make sure they weren't used there.
I also had to add a reference from the first project (with the views) to the second project (model and controller). After that it worked just fine to run the first project.
I continued to enable migrations in the model-controller-project and got a database connection without any problems. I could see that the controller managed to save data even though it was located in a different project.
I hope this can help you on the way...
EDIT:
Using the following code in the views from the first project (d2admin) worked fine even though the Car controller referred to exists in the second project. This link was used in the home (controller) / index (view) in the first project.
#Html.ActionLink("Go to the cars", "Index", "Car")
EDIT2:
This is the index view for the Car controller. The view is in d2admin and is referencing a controller in the PartyBiz project.
#model IEnumerable<PartyBiz.Models.Car>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.CarName)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.CarName)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.CarId }) |
#Html.ActionLink("Details", "Details", new { id=item.CarId }) |
#Html.ActionLink("Delete", "Delete", new { id=item.CarId })
</td>
</tr>
}
</table>
I acknowledge this is an old question with an already accepted answer; however, I ran into the same problem and was able to solve it and would like to share my experience.
From what I understand the following things are true:
d2admin is the code that handles the front end of the web site, and the controllers are used to drive the views and/or view models.
PartyWeb is used as an API at a domain level to interact with some datasource.
OrganizationController is the controller you're using to get data from the datasource to the d2admin project (and vice-versa)
With all of that in mind, arise the power of partial views!
Let's use the very simple View that would be located in d2admin/Views/SomeController.cshtml where SomeController is the folder that reflects the controller associated with these views.
<h3>A Very Basic View</h3>
#Html.Partial("_SomePartialView", OrganizationController.GetOrganizations())
Notice that this view has no model, and calls a partial and it's model is populated right there... and that's it! Now how would we write _SomePartialView.cshtml?
We will put it in the d2admin/Views/Shared folder, so the full path would be: d2admin/Views/Shared/_SomePartialView.cshtml. The file will look like
#model IEnumerable<PartyWeb.Models.Organization>
<div>
#foreach(var o in Model){
#Html.DisplayFor(modelItem => item.Id)
#Html.DisplayFor(modelItem => item.Name)
<br/>
}
</div>
As we can see this view will display some basic info assuming the following is our model found at PartyWeb/Models/Organization.cs
public class Organization
{
public int Id {get; set;}
public string Name {get; set;}
// some additional properties
}
and for the final bit of magic...
Within OrganizationController.cs we need to add the static action that will enable us to get the data to bind to our partial view's model. So we would add the following:
public class OrganizationController : ApiController
{
// Some Other Actions
[HttpGet]
public static List<Organization> GetOrganizations()
{
var dataSource = GetDataSource(); // Some Method that exposes the datasource
return ReadAllOrganizations(dataSource); // Some method that allows us to read all of the organiztions from the dataSource, i.e. some sql that executes against a database.
}
}