ASP.Net Core 3.1 and global view method - asp.net-core-mvc

The code #await Html.PartialAsync("~/Views/Shared/_Toast.cshtml", new ToastMessage() { Caption = "Hello", Message = "World" }) renders a partial view with arguments. It shows a Bootstrap toast message, which I intend to use everywhere.
Now is it possible to reduce that line to something like #MyHelpers.ShowToastMessage(new ToastMessage() { Caption = "Hello", Message = "World" })? I don't want to use View Components (overkill). Also #functions block seems to be local only.

You can custom a htmlhelper in your app.
namespace App.Helpers
{
public static class HtmlHelperExtensions
{
public static IHtmlContent ShowToastMessage(this IHtmlHelper htmlHelper, ToastMessage model)
{
string str = "<div> "+ model.Caption + " " + model.Message + " <div/>";
return new HtmlString(str);
}
}
}
In your view:
#using App.Helpers
#await Html.PartialAsync("~/Views/Shared/_Toast.cshtml", new ToastMessage() { Caption = "Hello", Message = "World" })
#Html.ShowToastMessage(new ToastMessage() { Caption = "Hello", Message = "World" })
If your _Toast.cshtml contains other html elements, then you can use TagBuilder Class to create.

Related

Is it possible to pass variables from the controller to the view and create multiple different URLs?

I have only recently begun using MVC. I have a situation in my view where I need to create several different links to several different places. Currently I am using a switch statement using their type as a parameter, and writing them out individually. Example here:
switch (link.Type)
{
case "type1": %>
<a href='<%= Url.RouteUrl("A", new { controller = "Controller1", action = "Action1",
param1 = x, param2 = y, newWindow = "yes" }) %>' target="_blank"><%: link.Name %></a> <%
break;
case "type2": %>
<a href='<%= Url.RouteUrl("B", new { controller = "Controller2", action = "Action2",
param1 = x, param2 = y, newWindow = "yes" }) %>' target="_blank"><%: link.Name %></a> <%
break;
}
As you can see from the example above, there are only very minor changes between the URLs. I believe I will have 10-20 of these types, and obviously solution where I would only have this once on the page would be ideal.
Is it possible to pass in variables to replace "A", "Controller1", "Action1"?
EDIT1: Erik already nailed my question but out of curiosity, is it also possible to supply parameter names through variables as well? So instead of having a parameter called "param2" I could have "param2" or "param3" decided through a variable?
EDIT2:
x and y in the example are ints that the used when the Url.RouteUrl link is clicked.
So for example, I have my two parameters that are projectId and recordId in my application. When the user clicks a link they are taken to a different controller/view and proceed from there. projectId is consistent in all links, but sometimes instead of recordId it might be tableId, articleId, etc. Is there a way to deal with these different parameters?
MVC is a Acronym for Model, View and Controller. The Controller should more often then not, create a Model and pass it into the view:
So you might have a class like:
public class IndexViewModel
{
public string SomeString { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
}
Then a controller like:
public ActionResult Index()
{
var model = new IndexViewModel();
model.SomeString = "A";
model.Controller = "Controller1";
model.Action = "Action1";
return View(model);
}
Then the view:
#model IndexViewModel
<a href='<%= Url.RouteUrl(Model.SomeString,
new { controller = Model.Controller,
action = Model.Action,
param1 = x, param2 = y, newWindow = "yes" }) %>'
target="_blank"><%: link.Name %></a>
Update 1: You can't assign a value to a class in the constructor/initializer of an anonymous class.
var myvar = new MyClass;
var myanon = new { x = 1, MyClass.Prop = 2 };
//------------------------^
// You can't do this, it doesn't make sense
Update 2: The UrlHelper.RouteUrl() Method has a number of overrides. Instead of trying to dynamically create an object for the signature RouteUrl(String, Object) use the signature RouteUrl(String, RouteValueDictionary):
model:
public class IndexViewModel
{
public string SomeString { get; set; }
public RouteValueDictionary RouteValues { get; set; }
}
controller:
public ActionResult Index()
{
var model = new IndexViewModel();
model.SomeString = "A";
model.RouteValues = new RouteValueDictionary();
model.RouteValues.Add("Controller", "Controller1");
model.RouteValues.Add("Action", "Action1");
model.RouteValues.Add("param1", x);
model.RouteValues.Add("param2", y);
return View(model);
}
view:
#model IndexViewModel
<a href='<%= Url.RouteUrl(Model.SomeString,
Model.RouteValues) %>'
target="_blank"><%: link.Name %></a>
public ActionResult Index()
{
var model = new IndexViewModel();
ViewBag.Link1="A";
ViewBag.Link2="B";
ViewBag.Link13"C";
ViewBag.Link3="D";
return View("ViewName",model);
}
Inside View
//.Link1
if(ViewBag.Link1)
{
}
if(ViewBag.Link2)
{
}
if(ViewBag.Link3)
{
}
if(ViewBag.Link4)
{
}

Display Template For Generics - View is not found

I have the following classes:
public class Widget
{
public string Name { get; set; }
}
GenericModel
public class GenericModel<T>
{
public List<T> Data { get; set; }
}
My Controller action is:
public ActionResult Simple()
{
var model = new GenericModel<Widget>()
{
Data = new List<Widget>
{
new Widget {Name = "a"}
}
};
return View(model);
}
And my view is:
#model MyApp.GenericModel<MyApp.Widget>
#{
ViewBag.Title = "Simple";
}
<h2>Simple</h2>
#Html.DisplayFor(m=>m)
I have a file called GenericModel.cshtml in Views/Shared/DisplayTemplate folder:
#model MyApp.GenericModel<MyApp.Widget>
<ul>
#for (int i = 0; i < Model.Data.Count; i++ )
{
<li>
#Html.EditorFor(m=> Model.Data[i].Name)
</li>
}
</ul>
This view can not be found. I see when I print out the name of the type of my model I get "GenericModel1". Seeing that, I renamed my template "GenericModel1.cshtml". This seems like a bit of a hack, is there an easier way to find this display template without resorting to this?
You have to set it in your viewstart:
#Code
Layout = "~/Views/Shared/DisplayTemplate.cshtml"
End Code
Note: The above is VB.
You can also pass it via your controller like this:
public ActionResult Simple()
{
var model = new GenericModel<Widget>()
{
Data = new List<Widget>
{
new Widget {Name = "a"}
}
};
return View("", "DisplayTemplate", model);
}

Invalid anonymous type member declarator. Anonymous ...?

I am building an htmlhelper extension but getting this error:
Invalid anonymous type member declarator. Anonymous type members must be declared
with a member assignment, simple name or member access.
I tried to cast the #User.IsInRole to a boolean but to no avail:(
this is the Razor markup:
#using htmlHelperstring.Models
#{
ViewBag.Title = "Home Page";
}
<ul>
#Html.MyActionLink(
"<span>Hello World</span>",
"about",
"home",
new { id = "123" },
new { #class = "foo",(bool)(#User.IsInRole("Chef"))}
)
</ul>
helper:
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace htmlHelperstring.Models
{
public static class LabelExtensions
{
public static IHtmlString MyActionLink(
this HtmlHelper htmlHelper,
string linkText,
string action,
string controller,
object routeValues,
object htmlAttributes,
bool UserAuthorized
)
{
var li = new TagBuilder("li");
if (UserAuthorized)
{
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
var anchor = new TagBuilder("a");
anchor.InnerHtml = linkText;
anchor.Attributes["href"] = urlHelper.Action(action, controller, routeValues);
anchor.MergeAttributes(new RouteValueDictionary(htmlAttributes));
li.InnerHtml = anchor.ToString();
}
else
{
li.InnerHtml = string.Empty;
}
return MvcHtmlString.Create(li.ToString());
}
}
}
Looks like you are missing a member assignment in:
new { #class = "foo",(bool)(#User.IsInRole("Chef"))}
What do you want to assign the boolean to?
You need something like this:
new { #class = "foo", HTMLATTRIBUTENAME = (bool)(#User.IsInRole("Chef"))}
Replace the HTMLATTRIBUTENAME with the attribute name you want to be set.
I don't write asp.net (never a single line, actually; so take it with a grain of salt); but I would suspect that the construct:
new { id = "123" }
(and the similar one beneath it) is what the message is referring to by "Anonymous type", and I have a few thoughts on why what you have might be erroneous (with the third feeling most likely).
First, If it's anything like C-style structs, you may need to use a '.' preceding the "member" identifier (which, to me, makes sense for that error message):
new { .id = "123}
Second, the wording of that error makes me wonder if in this environment you aren't allowed to pass in an anonymous object like that; and you need to assign it to a variable first and then pass the variable. Forgive any syntax errors:
#using htmlHelperstring.Models
#{
ViewBag.Title = "Home Page";
myID = new { id = 123 };
myClass = new { #class = "foo",(bool)(#User.IsInRole("Chef"))}
}
<ul>
#Html.MyActionLink(
"<span>Hello World</span>",
"about",
"home",
myID,
myClass
)
</ul>
Third, the syntax of new { #class = "foo",(bool)(#User.IsInRole("Chef"))} looks wonky to me. Perhaps (note adding a member name): new { #class = "foo", isChef = (bool)(#User.IsInRole("Chef"))}
I just made a stupid typo(end of the day etc ) it should be:
#{
ViewBag.Title = "Home Page";
myID = new { id = 123 };
myClass = new { #class = "foo"},(bool)(#User.IsInRole("Chef"))
}

ASP.NET MVC - Current Page highlighting in navigation

I'm wondering how is it possible to add a CSS Class to the current page in your navigation when using ASP.NET MVC 3? Here is my navigation in my _Layout.cshtml file:
<p>#Html.ActionLink("Product Search", "Index", new { controller = "Home" }, new { #class = "current" })
| #Html.ActionLink("Orders", "Index", new { controller = "Orders" })
| #Html.ActionLink("My Account", "MyAccount", new { controller = "Account" })
| #Html.ActionLink("Logout", "LogOff", new { controller = "Account" })</p>
As you can see I have 4 links in my navigation with the first one having the CSS class "current" applied to it, I'd like to be able to add/remove this class to the different links in my navigation depending on which page the user is at. Is this possible?
Cheers
You can do this
#{
var currentController = ViewContext.RouteData.Values["controller"] as string ?? "Home";
var currentAction = ViewContext.RouteData.Values["action"] as string ?? "Index";
var currentPage = (currentController + "-" + currentAction ).ToLower();
}
#Html.ActionLink("Product Search", "Index", "Home", null,
new { #class = currentPage == "home-index" ? "current" : "" })
#Html.ActionLink("MyAccount", "MyAccount", "Account", null,
new { #class = currentPage == "account-myaccount" ? "current" : "" })
I would recommend using an extension method for this. Something like:
public static HtmlString NavigationLink(
this HtmlHelper html,
string linkText,
string actionName,
string controllerName)
{
string contextAction = (string)html.ViewContext.RouteData.Values["action"];
string contextController = (string)html.ViewContext.RouteData.Values["controller"];
bool isCurrent =
string.Equals(contextAction, actionName, StringComparison.CurrentCultureIgnoreCase) &&
string.Equals(contextController, controllerName, StringComparison.CurrentCultureIgnoreCase);
return html.ActionLink(
linkText,
actionName,
controllerName,
routeValues: null,
htmlAttributes: isCurrent ? new { #class = "current" } : null);
}
Then you can use it in your View by including the namespace of your extension and just calling your method:
#using MyExtensionNamespace;
...
#Html.NavigationLink("Product Search", "Index", "Home")
| #Html.NavigationLink("Orders", "Index", "Orders")
| #Html.NavigationLink("My Account", "MyAccount", "Account")
| #Html.NavigationLink("Logout", "LogOff", "Account")
This has the benefit of keeping your razor a little cleaner and is easily reusable in other views.
#{
var controller = ViewContext.RouteData.Values["controller"].ToString();
var action = ViewContext.RouteData.Values["action"].ToString();
var isActiveController = new Func<string, string, string, string, string>((ctrl, act, activeStyle, inactiveStyle) => controller == ctrl && action == act ? activeStyle : inactiveStyle);
}
Then in your class attribute in your HTML you can do:
class="#isActiveController("controlername","action","activecssstyleclass","inactiveccsstyle")"
Just an other way of #dknaack his answer.. bit more generic and less functionality to repeat in your code.
In my case,assume I have a Home page and a menu.
Add a ViewBag.Active as a placeholder in Home page like this:
#{
ViewBag.Title = "Home";
ViewBag.Active = "Home";
}
Then place it to your li class as a condition to active it or not:
<li class="#(ViewBag.Active=="Home"? "active" : "")">
<span>#ViewBag.Title</span>
</li>
I used this tutorial to get this done, it's a lot simpler to understand and takes 2 minutes
Hightlight Active menu item
You can also override the AnchorTagHelper (the default <a> tag helper) to create your own tag helper. The advantage is that it already has all required information by providing the asp-controller and even allows for autocomplete etc. by your IDE.
This is my TagHelper:
public class NavAnchorTagHelper : AnchorTagHelper
{
public NavAnchorTagHelper(IHtmlGenerator generator) : base(generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
var contextController = (string)ViewContext.RouteData.Values["controller"];
if (contextController?.Equals(this.Controller) == true)
{
output.AddClass("text-dark", HtmlEncoder.Default);
}
output.TagName = "a";
}
}
And I use it like that:
<nav-anchor class="nav-link" asp-controller="MyController" asp-action="Index">Test</nav-anchor>

Best way to sort a DropDownList in MVC3 / Razor using helper method

Hi so I'm pretty new to MVC3 and Razor and I've been trying to get my head around it the past few days. I've been given a task by my project architect to create a helper method that sorts a drop down list in an MVC View. I have a View that retrieves various data from a Controller and I'm returning some values that I want to appear in a drop down list. I've been told not to sort it in the Controller and also to pass the field that we want to sort by into the helper method. I could do it like below but the architect wants to keep the view free of c sharp code:
#Html.DropDownListFor(model => model.StudyName, new SelectList(ViewBag.StudyTypes, "Value", "Text").OrderBy(l => l.Text))
So I've created some sample code and some extension methods to try and get it to work. My idea is to replicate the existing Html.DropDownList method and allow the passing of 'object htmlAttributes' so I can set the style as part of the method call.
Here's my code so far. I'm returning the data for the drop down in ViewBag.StudyTypes in the Edit Controller method:
public ActionResult Edit(int id)
{
IEnumerable<SelectListItem> mySelectList = new List<SelectListItem>();
IList<SelectListItem> myList = new List<SelectListItem>();
for (int i = 0; i < 5; i++)
{
myList.Add(new SelectListItem()
{ Value = i.ToString(), Text = "My Item " + i.ToString(), Selected = i == 2 }
);
}
mySelectList = myList;
ViewBag.StudyTypes = mySelectList;
StudyDefinition studydefinition = db.StudyDefinitions.Find(id);
return View(studydefinition);
}
Here's my View code:
#model MyStudyWeb.Models.StudyDefinition
#using MyStudyWeb.Helpers
#{
ViewBag.Mode = "Edit";
}
<div>
#Html.DropDownListSorted(new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
#Html.DropDownListSorted("MyList", new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
</div>
Finally below are the extension methods I'm trying to get to work. The first extension method does nothing, I just get a blank space at that point in the View. The second method kind of works but it's ugly. For the 3rd method I don't know how to specify an 'order by' parameter as the OrderBy on an IEnumerable expects a Linq expression.
namespace StudyDefinition.Helpers
{
public static class HtmlHelperExtensions
{
// 1st sort method: sort the passed in list and return a new sorted list
public static SelectList DropDownListSorted(this HtmlHelper helper, IEnumerable<SelectListItem> selectList)
{
var x = new SelectList(selectList.ToList()).OrderBy(l => l.Text);
return x as SelectList;
}
// 2nd sort method: return IHtml string and create <select> list manually
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList)
{
StringBuilder output = new StringBuilder();
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
// 3rd sort method: pass in order by parameter - how do I use this?
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList, string orderBy)
{
StringBuilder output = new StringBuilder();
//How do I use the orderBy parameter?
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
}
}
I really don't know the best approach to take, there may be a much simpler way that I'm totally missing and I might be at the point where I can't see the wood for the trees anymore. Some questions
Should I return a SelectList or an MvcHtmlString, or something else entirely?
For the first extension method how do I get the returned SelectList to render in the View?
How to I pass in a parameter to my extension methods that specifies the sort order?
How do I pass an 'object htmlAttributes' parameter, and how do I apply this object / parameter to the SelectList?
If anyone has some ideas or suggestions then I'd appreciate some feedback :)
The first and most important part of your code would be to get rid of any ViewBag/ViewData (which I personally consider as cancer for MVC applications) and use view models and strongly typed views.
So let's start by defining a view model which would represent the data our view will be working with (a dropdownlistg in this example):
public class MyViewModel
{
public string SelectedItem { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
then we could have a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// I am explicitly putting some items out of order
Items = new[]
{
new SelectListItem { Value = "5", Text = "Item 5" },
new SelectListItem { Value = "1", Text = "Item 1" },
new SelectListItem { Value = "3", Text = "Item 3" },
new SelectListItem { Value = "4", Text = "Item 4" },
}
};
return View(model);
}
}
and a view:
#model MyViewModel
#Html.DropDownListForSorted(
x => x.SelectedItem,
Model.Items,
new { #class = "foo" }
)
and finally the last piece is the helper method which will sort the dropdown by value (you could adapt it to sort by text):
public static class HtmlExtensions
{
public static IHtmlString DropDownListForSorted<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> items,
object htmlAttributes
)
{
var model = helper.ViewData.Model;
var orderedItems = items.OrderBy(x => x.Value);
return helper.DropDownListFor(
expression,
new SelectList(orderedItems, "Value", "Text"),
htmlAttributes
);
}
}
Just add in the sorting before you return the items to the dropdown list.
Do this:
Models: StudyViewModel.cs
public class StudyViewModel {
public string StudyName { get; set; }
public string StudyTypes { get; set; }
}
Controller: StudyController.cs
using System.Web.Mvc;
public class StudyController
{
public List<SelectListItem> studyTypes()
{
List<SelectListItem> itemList = new List<SelectListItem>();
for (var i=0; i<5; i++)
{
itemList.Add = new SelectListItem({
Value = i.ToString();
Text = "My Item";
});
}
// You can sort here....
List<SelectListItem> sortedList = itemList.OrderBy(x=>x.Text);
return sortedList;
}
public ActionResult Edit(int id)
{
//You won't need this because you get it using your
//controller's routine, instead
//ViewBag.StudyTypes = studySlots.OrderBy(e => e.Value);
//-- unless you need to add these values to the model for
// some reason (outside of filling the ddl), in which case....
// StudyViewModel svm = new StudyViewModel();
// svm.StudyTypes = studySlots.OrderBy(e => e.Value);
// svm.StudyName = "My Item";
// return View(svm);
// Otherwise, just....
return View();
}
}
View: Edit.cshtml
#Html.DropDownListFor(model => model.StudyName)
.OptionLabel('Select...')
.DataTextField('Text')
.DataValueField('Value')
.Datasource(source =>
{
// This is where you populate your data from the controller
source.Read(read =>
{
read.Action("studyTypes", "Study");
});
})
.Value(Model.StudyName != null ? Model.StudyName.ToString() : "")
)
This way will avoid ViewBags and just use a function to fill in the values, directly.
If you are using a database you can use a query to define the sort element
using (BDMMContext dataContext = new BDMMContext())
{
foreach (Arquiteto arq in dataContext.Arquitetos.SqlQuery("SELECT * FROM Arquitetos ORDER BY Nome"))
{
SelectListItem selectItem = new SelectListItem { Text = arq.Nome, Value = arq.Arquiteto_Id.ToString() };
//
list.Add(selectItem);
}
}

Resources