Pass parameter to Partial View in ASP.NET Core - asp.net-core

On an ASP.NET Core 2.0 application I need to render a partial view and pass a few parameters:
#Html.Partial("Form", new { File = "file.pdf" })
On the partial view I tried to access it using:
#Model.File
And I get the error:
RuntimeBinderException: 'object' does not contain a definition for 'File'
If I simply use on my partial:
#Model
I get the following printed on the page:
{ File = file.pdf }
So there the model is being passed and there is a property File in it.
So what am I missing?

You are passing untyped (anonymous type) data to partial view. You cannot use #Model.File. Instead, you will need to use ViewData's Eval method to retrieve the value.
#ViewData.Eval("File")
Traditional approach is to create a strongly typed ViewModel class, and pass it to the partial view. Then you can access it as #Model.File.
public class SampleViewModel
{
public string File { get; set; }
}
#Html.Partial("Form", new SampleViewModel { File = "file.pdf" })
Inside Partial View,
#model SampleViewModel
<h1>#Model.File</h1>

You should have dynamic as the model of your partial view, this way, you can pass everything - like your anonymous object - and it will just work. Add:
#model dynamic
To the Form.cshtml file.

When you do new { File = "file.pdf" }, you are passing an object that contains an attribute file. Since this is of type object, you can't access any of its variables in c# directly. There some ugly codes to access a fields from an object such as the one that can be found here: C# .NET CORE how to get the value of a custom attribute?
However in this case the most recommended way (for safety) is the create a class and pass an object of that class.
So if you create the following class:
public class MyFileInfo{
public string File { get; set }
}
Then you can create your object by passing:
#Html.Partial("Form", new MyFileInfo{ File = "file.pdf" })
In your partial view, at the beginning, define your model class
#model MyFileInfo
Then in the same file you will now be able to access
#Model.File

For the sake of completeness: you can pass arbitrary variable via a ViewDictionary
#Html.Partial("_Partial", model, new ViewDataDictionary(ViewData) { { "MyVarName", someValue } })
And then access it like this in the partial:
ViewData["MyVarName"]
Another option, you can simply set a ViewData var before calling the partial, and it will be passed on to it
#{
ViewData["MyVarName"] = "hello";
}
#Html.Partial("_Partial", model)
However, strongly typed models are much easier to work with.

Related

Umbraco custom controller not using declared model type

Umbraco 9 - I've created a page type called SiteSearch and a controller to hijack requests to pages of that page type. This all works correctly.
The controller gets an IEnumerable from a very simple search service and sets it on a ViewModel, which is then passed to the view.
However, I simply cannot get the view to respect the model declaration. I am getting the error:
ModelBindingException: Cannot bind source type
Maysteel_Web.ViewModels.SiteSearchViewModel to model type
Umbraco.Cms.Core.Models.PublishedContent.IPublishedContent.
It seems to be forcing my view to use an instance IPublishedContent (singular), even though I'm declaring the model as my custom object. I've tried changing the model declaration to string just to see what would happen:
InvalidOperationException: The model item passed into the
ViewDataDictionary is of type
'Maysteel_Web.ViewModels.SiteSearchViewModel', but this
ViewDataDictionary instance requires a model item of type
'System.String'.
There it recognized the model, but when I declare my custom object as a model, it goes back to trying to bind to IPublishedContent (singular). I've verified that the model I'm passing is not null and actually has results. I'm not sure what else to try. Can anyone help me undertand why this is happening?
SiteSearchController action:
public override IActionResult Index()
{
var searchPage = new SiteSearch(CurrentPage, _publishedValueFallback);
var results = _searchService.QueryUmbraco("about");
var viewModel = new ViewModels.SiteSearchViewModel();
viewModel.Results = results;
return View("~/Views/SiteSearch/index.cshtml", viewModel);
}
View:
#model Maysteel_Web.ViewModels.SiteSearchViewModel
#{
Layout = "/Views/Main.cshtml";
}
<h1>Site Search Page</h1>
ViewModel:
public class SiteSearchViewModel
{
public IEnumerable<IPublishedContent> Results { get; set; }
}
I just figured it out. My layout view had the following line in it:
#inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
Once I removed that, I no longer get the error. It must have been forcing the ViewModel to be IPublishedContent.

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

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.

InvalidOperationException rendering ViewComponent in Strongly-Typed View

Recently updated dotnet core 1.0.1 to 1.1 and ViewComponent in MVC starts failing with the below exception:
InvalidOperationException: One or more errors occurred. (The model item passed into the ViewDataDictionary is of type 'App.Models.HomeViewModel', but this ViewDataDictionary instance requires a model item of type 'App.Components.LoginViewComponent'.)
The Index.cshtml renders LoginViewComponent:
#model App.Models.HomeViewModel
<html>
#Component.InvokeAsync("LoginViewComponent")
</html>
Views/Home/Index.cshtml
The ViewComponent LoginViewComponent/Default.cshtml is just a class that displays it's model's info:
#model App.Components.LoginViewCompoent
<body>
<div>#Html.DisplayFor(v=>v.LoginName)</div>
</body>
Views/Shared/Components/LoginViewComponent/Default.cshtml
It renders fine when the #model directives is removed from Default.cshtml.
Isn't ViewComponent suppose to be separated and agnostic from the parent view that is wrapping it? From the exception it seems that it would be require to declare the LoginViewComponent ViewComponent in HomeViewModel class in order to render it.
Couldn't find any change note on this on asp.net core github.
Comment and help on this would be greatly appreciated.
I've came across the same error, someone in my team updated Microsoft.AspNetCore.Mvc version from 1.0.0 to 1.1.0 and some of the components I had in Strongly-Type views started throwing
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'My.StronglyView.ObjectType', but this ViewDataDictionary instance requires a model item of type 'My.ViewComponent.ObjectType'.
I am not sure if this change was intentional, but it is definitely not what we would expect.
I haven't got the time to research about the reasons of this 'breaking' change but I came with a solution.
Thing is, if you pass a null object to your ViewComponent.View() method, we get this exception. Any non-null object passed through it, would update the ViewData.ModelExplorer and the correct object-type would have been registered, avoiding this exception.
Using Tester-Doer pattern, same pattern used in some classes of .Net Framework, We can now pass a non-null object to the ViewComponent and use it and its wrapped object as We need.
What I did was, I created a interface IViewComponentModel<T> as class ViewComponentModel<T> as below:
// Interface for ViewComponentModel
public interface IViewComponentModel<T>
where T : class
{
T Data { get; }
bool HasData();
}
// ViewComponentModel class for the Strongly-Type View Component
public class ViewComponentModel<T> : IViewComponentModel<T>
where T : class
{
public T Data { get; private set; }
public ViewComponentModel(T data)
{
Data = data;
}
public bool HasData() => Data != null;
}
In my ViewComponent class implementation, I return View(new ViewComponentModel<AnyReferenceType>(myModel));
public async Task<IViewComponentResult> InvokeAsync()
{
var myViewData = _myService.GetSomeObjectForMyViewComponent();
var model = new ViewComponentModel<MyViewDataType>(myViewData);
return await Task.FromResult(View(model));
}
And finally, in my Component View (.cshtml) I have the code below:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#model ViewComponentModel<MyViewDataType>
Now you can do whatever you want with this object inside of the view and to use your real model, just call #Model.Data and voilĂ .
In this way, We will never pass a null object to the ViewComponent and it won't 'inherit' the object type from the View.
Hopefully it helps!
asp.net-core asp.net-core-mvc dotnet-core asp.net-core-1.1
Simple solution is that, in ViewComponent class check null for view model, and if view model is null, then return empty Content result as following:
public async Task<IViewComponentResult> InvokeAsync()
{
var vm = some query for ViewModel;
if(vm != null)
{
return View(vm);
}
return Content("");
}
By default it will try to pass in the model of the "Parent View" ,not sure what you would call that, try updating your code like this
#Component.InvokeAsync("LoginViewComponent", new App.Components.LoginViewComponent())
so that partial view can have what it needs, in order to serve up it's content.

Pattern for passing common data to _layout.cshtml in MVC4.5

I am trying to come up with the best pattern for passing data to my _layout.cshtml page.
I am toying with creating a common base class from which all view specific models derive. This base class would be recognized by my _layout.cshtml and used to fill in details about the user and load proper images in the header, etc. For example, here is a snippet of it.
public abstract class ViewModelBase
{
public string Username { get; set; }
public string Version { get; set; }
}
At the top of my _layout.cshtml I have...
#model MyProject.Web.Controllers.ViewModelBase
I need a common area to hydrate the information required by the model, and am planning to use the following pattern...
Each action method creates and hydrates a model derived from
ViewModelBase.
The action completes.
I create a ActionFilterAttribute and override OnActionExecuted to cast the
current Result to ViewModelBase.
If the conversion is successful, then I populate the ViewModelBase details with the relevant data.
Here are my questions...
Is the use of a ActionFilterAttribute (OnActionExecuted) a good pattern for what I am trying to do?
I am not able to see how to get the Result created in the action from the HttpActionExecutedContext. How is this done?
I follow the same approach and use a base ViewModel class which all my other viewModels inherit from.
Then, I have a base controller that all controller inherit from. In there, I have one method that takes care of initializing the view model:
protected T CreateViewModel<T>() where T : ViewModel.BaseViewModel, new()
{
var viewModelT = new T {
HeaderTitle = "Welcome to my domain",
VisitorUsername = this.VisitorUsername,
IsCurrentVisitorAuthenticated = this.IsCurrentVisitorAuthenticated,
//...
};
return viewModelT;
}
Then on each controller, when I want to create the view model, I simply call the base controller's method:
var vm = base.CreateViewModel<MyPageCustomViewModel>();