ASP.NET Core MVC : model binding and complex types - asp.net-core

I have a model that has a property which is a class (complex type) which, in turn, has properties that are themselves complex types; turtles all the way down.
I feel that I ought to be able to do
<input type="hidden" asp-for="#Model.MyComplexType" />
But apparently not so out-of-the-box.
Is there a simple solution? I really don't want to have to (can't be arsed) to write out an <input type="hidden"> for every property.

You can mark your complex type's properties with the HiddenInput attribute:
public class ComplexType
{
[HiddenInput]
public string Property {get;set;}
}
Then in your view call
#Html.EditorForModel(x => x.MyComplexType)
This will add all your properties as hidden.

Related

Instantiating ModelExpression directly

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);

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

passing view model from page to user control in windows store app

In a Windows Store split app, I want to pass a view model from a page to a user control. The scenario is that I want to reuse some common xaml in multiple pages, using a UserControl like a view.
In the main page:
<common:LayoutAwarePage
...
DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}"
... >
<views:MyUserControlView Model="{Binding ViewModel}" />
...
In the user control code:
public sealed partial class MyUserControlView : UserControl
{
public static readonly DependencyProperty ModelProperty =
DependencyProperty.Register("Model", typeof(MenuSource),
typeof(MyUserControlView), null);
...
public ModelType Model
{
get
{
return this.GetValue(ModelProperty) as ModelType ;
}
set
{
this.SetValue(ModelProperty, value);
}
}
The Model setter is never called. How do I hook up the user control to the parent page's view model?
Or, is there a better way to implement shared views for use in pages?
Thanks.
-John
Correct binding would be:
<views:MyUserControlView Model="{Binding}" />
You've already set DataContext for the page above. All bindings are relative to the current DataContext.
The setter still won't be called, though. It is just a wrapper to access the DependencyProperty from code. Binding will call SetValue directly.
Depending on your requirements you might not even need to define your own Model DependencyProperty. Each control automatically inherits DataContext from its parent control. In your example above the user control already has its DataContext set to the same view model as the page.

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

super() type functionality on ORM CFC

When I use CF9's ORM feature and generate an explict setter for my ORM CFC, is there anyway to call the default funcitionailty of the ORM CFC after i have done the work needed in the method. For example i am looking for something like this. Obviosuly the code will not run , and super is the wrong concept since the ORM CFC isnt inherting anything, but thats the type of functionality I am looking for.
public void setMovie(String movie){
if(movie == "inception"){
ORMCFC.super().setMovie("Greatest movie ever made")
}else{
ORMCFC.super().setMovie(movie)
}
In your model CFC for the ORM you can specify additional "decorator" functions.
component persistent="true" table="Movie" schema="dbo" output="false"
{
/* properties */
property name="MovieNo" column="MovieNo" type="numeric" ormtype="double" fieldtype="id" ;
property name="Name" column="Name" type="string" ormtype="string" ;
/* decorator */
public void function setMovie(name)
{
if(name == "inception"){
setName("Greatest movie ever made")
}else{
setName(name)
}
}
}
Otherwise if you need to (using your example) setMovie() you will need to do an EntityLoad or create a new entity to set a value to.