Set a default value to the model being passed to Partial View - c#

I have a partial view that is called from another partial view (kind of nested partial views).
The outer partial view is called Company and the inner partial view is a custom control called searchHelp. Both accept a parameter.
Now the Company view gets a parameter of type company and searchHelper accepts an optional string. This part works fine as I am testing the model value for null and assigning is default text as #((Model==null)?"Enter Text":Model) when used in other views even without passing a parameter.
In my case of nested views, if I dont provide a string as model for searchHelper then it takes company as model from the outer view i.e company and gives an error.

The #model definition is not a value setter, it's merely telling Razor what type of view to instantiate. You can't define a default value here. If you don't pass a model to your partial, then it will use the model of the parent view, which is Company in this case. Company is not a string, obviously, so you get that error. If you want to pass a default value for the partial, do that in the second parameter to Html.Partial:
#Html.Partial("searchHelp", Model.SomeStringProperty ?? "Enter Text")

You can assign a default value to the string-as-model from where it's called in the view:
//null coalesce to default string value:
#Html.Partial("searchHelp", Model.searchHelp ?? "default value")
...though you might do better using an htmlhelper, where you can define the default value just one time:
public IHtmlString SearchHelp(this HtmlHelper html, string searchHelp = "default value")
{
// make html here
}
Then
#Html.SearchHelp(Model.searchHelp);

Related

In asp helper tags, what is the difference between Model.PropertyName and just PropertyName

Here is an example:
cshtml >>
<label>List of products: </label>
<select asp-items="Model.ListOfProducts" asp-for="ProductToSearch">
<option value="">All</option>
</select>
cshtml.cs >
public class IndexModel : PageModel
{
...
public SelectList ListOfProducts{get; set;}
[BindProperty(SupportsGet=true)]
public string ProductToSearch{get; set;}
...
}
In cshtml why is one named Model.ListOfProducts but other one is named just ProductToSearch.
What does Model in this case do?
It has to do with the actual types for those attributes on the tag helper(s). The asp-for attribute, for example, is backed by a property on the tag helper class that is typed as ModelExpression. As such, what you're passing to the attribute in the view is expected to be something that can be interpreted as a ModelExpression, i.e. a representation of a specific property at some level on the model. In other cases, such as with asp-items, the type is IEnumerable<SelectListItem>, so you're expected to pass a value that is of that type.
Simply, the syntax of #Model.Property is a dereference: you're retrieving the value of that property, whereas when you just do Property, you're passing an expression representing that property.
There is a scenario that blurs the lines a bit: when the model is a list and you're specifically trying to reference a particular item in that list as an expression. In that scenario, something like [0].Property is not a valid expression, so you must use #Model[0].Property.
The list you supply to asp-items could potentially come from somewhere external to the model, so you have to specify the full namespace/class path to the object (or method to generate the object) you want to use.
Whereas in the asp-for you're expected to always specify a property from the current model, so there's no need to prefix it.

How to access properties in the model passed to the view?

I'm sending the following model to my view.
return View(new { url = returnUrl });
In the view, I'm don't want to specify any particular class for my object (since I wish to keep it flexible and elastic for now). So, the #Model is the apparently an object and as such, it's got no url property. Hence the error.
Additional information: 'object' does not contain a definition for 'url'
Now, I do know that the thing inside the object has url property. I have assigned it to it and I also see it when watching the variable as the exception's been thrown.
My question is how to access the field. Is my only option declaring a class and type the model using #model Something? I can't use as keyword to type it to var...
In "plain" C# we can do something like this.
var some = new {thing = "poof"};
string output = some.thing;
How do I do the equivalent of it in CSHTML file under Razor?
Strongly-typed view models are the way to go. Create a type that suits the needs of the view and treat reusability/duplication as a secondary concern.
However, let me explain why your attempt did not work.
It is legal to pass an anonymous type--even between assemblies[1]--as long as it is cast to object. In fact, the MVC framework assemblies consume anonymous types in many helper methods. Those anonymous types are then evaluated using reflection (optimized by caching).
1: I believe there are some caveats to this, and it certainly isn't good practice in most cases.
A view is compiled into a class so that it can be executed. Part of the class's contract is the type of model it expects, as indicated by #model in your view markup.
This presents a problem with anonymous types, as you cannot indicate their type in your view and type object doesn't expose the properties you set when declaring the type. Thus, you end up with a view that only knows that its model is an object.
Again, use strongly-typed models, or the ViewBag if you truly only need one or two values.
However, to prove that the anonymous type can be passed to the view, look at this (horrible) example:
Controller
return View( new { property1 = "hello world"} );
View
#model object
#{
var rvd = new RouteValueDictionary( Model );
}
#rvd["property1"]
We passed an anonymous type to the view as an object, and then read the object's properties using RouteValueDictionary.
You can use ViewData and ViewBag to send objects to the view page, in your case you can write in the controller something like this:
ViewData["url"] = url ; //Or whatever
return View();
Now in the view you can simply use your object example:<div>#ViewData["url"]</div>
But mainly in MVC it is more recommended to use strongly typed View Models
You may want to look into using the dynamic type in C#. See https://msdn.microsoft.com/en-us/library/dd264736.aspx for details.
While the standard would be to use a strongly-typed view model, there are some scenarios where you might want to use dynamic as your model type (or as a property of your strongly-typed view model), such as in a CMS where the properties are built dynamically by the CMS Provider.
Example view:
#model dynamic
<p>
Url: #Model.url
</p>

How do I use DisplayNameForModel?

I tried using this as a header for a view but it returns an empty string. In the Razor layout, I have something like:
#model IEnumerable<MVCApp.Models.Model>
<h2>#Html.DisplayNameForModel()</h2>
Do I need to set something on the model definition itself? I tried a data annotation [Display(Name="Model Name")] but it is a build error:
Attribute 'Display' is not valid on this declaration type. It is only valid on 'method, property, indexer, field, param' declarations.
The documentation DisplayNameExtensions.DisplayNameForModel Method is terse. The syntax calls for a parameter, but says:
No overload for method 'DisplayNameForModel' takes 1 arguments
As the Usage section says "When you use instance method syntax to call this method, omit the first parameter"
So, how do I use this method to return something?
I just used the default MVC 5 template project in VS2013 and have the #Html.DisplayNameForModel() working with no issues.
First, you are using the wrong data annotation on your view model. You want to use [DisplayName("My Model Name")] and not [Display()]
[DisplayName("Test View Model")]
public class TestViewModel
{
public string TestProperty { get; set; }
}
Second, the html parameter you are seeing on MSDN is a required parameter for any Html helpers in MVC. You do not have to pass anything for this value, the view engine does this for you. So, in your view, you would use the following to get the Display Name that you set on the model as so.
<h2>#Html.DisplayNameForModel()</h2>
Now, your result should output the display name attribute you set in your html. *Note the Test View Model above the Log In text.
try
#Html.DisplayNameFor(m => m.Name)

How do you put a ViewBag item into a Text Box?

I have the following code and I get an error saying:
has no applicable method named 'TextBoxFor' but appears to have an extension method by that name.
My Code:
#Html.TextBoxFor(ViewBag.taglist)
Why don't you use strongly typed model in your view instead of ViewBag. This will make your life easier.
In fact, you must use a model to with TextBoxFor, otherwise it just won't work. See the definition of TextBoxFor - as a second parameter it takes a lambda expression that takes a property form a model.
If you want just a text box, two options:
#Html.TextBox("NameOfTheTextbox", (String)ViewBag.SomeValue)
or just go
<input type="text" value="#ViewBag.SomeValue" />
No complex solutions required.
I agree with other suggestions of using a strongly-typed model, because the compile-time error support is so much better than debugging exceptions. Having said that, in order to do what you want, you can use this:
#Html.TextBox("NameOfTextBox", (string)ViewBag.taglist)
Update: A Simple Example
Now that you've provided some details in your comments, I've taken a guess at what you might be doing, in order to provide a simple example.
I'm assuming you have a list of tags (like SO has per question) that you'd like to display neatly in a textbox, with each tag separated by a space. I'm going to assume your Tag domain model looks something like this:
public class Tag
{
public int Id { get; set; }
public string Description { get; set; }
}
Now, your view will need a list of the tags but will likely need some other information to be displayed as well. However, let's just focus on the tags. Below is a view model to represent all the tags, taking into account that you want to display them as a string inside a textbox:
public class SomeViewModel
{
public string Tags { get; set; }
// Other properties
}
In order to get the data you want you could grab all of the tags like this:
public ActionResult Index()
{
using (YourContext db = new YourContext())
{
var model = new SomeViewModel();
model.Tags = string.Join(" ", db.Tags.Select(t => t.Description).ToList());
return View(model);
}
}
Notice how I'm directly passing model to the view.
The view is now very simple:
#model SomeViewModel
#Html.EditorFor(m => m.Tags)
The model directive is what signifies that a view is strongly-typed. That means this view will expect to receive an instance of SomeViewModel. As you can see from my action code above, we will be providing this view the type that it wants. This now allows us to make use of the strongly-typed HtmlHelper (i.e. Html.XxxFor) methods.
In this particular case, I've used Html.EditorFor, as it will choose an appropriate input element to render the data with. (In this case, because Description is a string, it will render a textbox.)
You cannot use Html.TextBoxFor without explicitly setting a type for your model within the view. If you don't specify a type it defaults to dynamic. If you want to do model binding then you must use an explicit type rather than a dynamic type like ViewBag. To use Html.TextBoxFor you must define a model type that defines the property that you wish to bind. Otherwise you have to use Html.TextBox and set the value manually from ViewBag. As others have said, you will make your life much easier if you use a statically typed model and take advantage of the inbuilt MVC model binding.
You have to use a lambda expression to select the property, plus you will have to cast the ViewBag member to the correct type.
#Html.TextBoxFor(model => (string)ViewBag.taglist)

Custom type-dependent template loader

As you might know, ASP.NET MVC has support for custom view overrides for model fields within views. There are special folders in the Views folder called Views\Shared\EditorTemplates, Views\Shared\DisplayTemplates and so on, and these folders can contain files like Views\Shared\EditorTemplates\String.cshtml, which will override the default view used when calling #Html.EditorFor in a view with a model with a String field.
What I want to do is to use this functionality for a custom kind of templates. I want to have a folder like Views\Shared\GroupTemplates that may contain e.g. Views\Shared\GroupTemplates\String.cshtml and Views\Shared\GroupTemplates\Object.cshtml, and I want to create a HtmlHelper method that allows me to call for example Html.GroupFor(foo => foo.Bar), which will load the template in String.cshtml if Bar is a String property, and the template in Object.cshtml otherwise.
Full example of the expected behavior; if Views\Shared\GroupTemplates\String.cshtml contains this:
#model String
This is the string template
... and Views\Shared\GroupTemplates\Object.cshtml contains this:
#model Object
This is the object template
I have a model like:
class Foo
{
public bool Bar { get; set; }
public String Baz { get; set; }
}
And a view in Views\Foo\Create.cshtml like:
#model Foo
#Html.GroupFor(m => m.Bar)
#Html.GroupFor(m => m.Baz)
When I render the view Create.cshtml, the result should be this:
This is the object template
This is the string template
How should GroupFor be implemented?
The thing is that you can easily specify your view location like that
html.Partial("~/Views/Shared/GroupTemplates/YourViewName.cshtml");
or even override default behaviour by implementing custom view engine, for an example see this blog A Custom View Engine with Dynamic View Location
But you also want to reuse the logic which determines the view name based on its model type. So that if a view with String name doesn't exist an Object view is picked up. Which means going through parent classes.
I've had a look how EditorFor is implemented:
public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
return html.TemplateFor<TModel, TValue>(expression, null, null, DataBoundControlMode.Edit, null);
}
It uses TemplateFor method which is internal and you can't just reuse it.
So I can only see of two options:
Implement you custom logic by checking if a view file with a correct name exists by trying model type name and its parent classes. And if you find a proper view just use Partial extension in your helper.
Try to use reflection to call internal method. But this approach is more like a hack than a solution.
Hope it helps!

Categories