Im a newbie to MVC and trying to use the web navigator class in my MVC application. http://polymorphicpodcast.com/shows/webnavigator/
The class enables us to have stongly typed urls in a central class file.
I have 3 questions:
Is this the best way of storing
strongly typed urls in MVC or does
MVC has some special helper methods
which does the same thing.
What is the best place for the class
file to go in e.g VIEW/MODEL or
CONTROLLER
Below is a sample of the Navigation
class which returns a proper url for
an image file.
Instead of passing the UrlHelper object to the I want to
run this
class in the System.Web.Mvc.ViewPage
context (current context). What is
the best way of doing this.
Sample navigator class for images:
using System.Web;
using System.Web.Mvc;
public class IMG
{
public string AjaxLoading(UrlHelper urlHelper)
{
return urlHelper.Content("~/Images/loading2.gif");
}
}
To answer your questions:
There are many ways to create navigation links for you ASP.NET MVC, whatever works for you is the best.
Most would answer that class files should be placed in Model folder. I found it more meaningful to place ViewModel classes in a separate folder and classes used throughout the application (business logic/meat of the application) in a separate file.
What you're trying to accomplish seems like a job for extension methods. Here is a good tutorial: http://www.dotnetcurry.com/ShowArticle.aspx?ID=406&AspxAutoDetectCookieSupport=1
What you're doing is on the right track, however, you need to create static classes and static functions/methods for this to work properly.
http://msdn.microsoft.com/en-us/library/bb383977.aspx has some general info on extension methods.
One quick note: To allow usage of all the extension methods you create, you'll need to reference the class/namespace that you placed them in.
There are two methods of doing this:
Assuming you've placed your extension methods in MvcApplication1.MyExtensionMethods, Add the following after the
<page>
<namespaces>
tag in your application's web.config (Not the View's web.config file)
<add namespace="MvcApplication1.MyExtensionMethods"/>
Doing so will allow the extension methods to be used in all of your views (.aspx/.ascx) files.
Place
<%# Import Namespace="MvcApplication1.MyExtensionMethods" %>
at the top of your .aspx/.ascx files. You would need to do this for every file you need to use the extension methods on (not efficient)
The following is what I implement and it has served me well thus far.
NavigationLink.cs
public class NavigationLink
{
string Text {get; set;}
RouteValueDictionary Routes {get; set;}
string RouteName {get; set;}
}
NavigationLink.ascx (Place in shared folder for easy access in entire application)
(Note: I wrap the links in < li> < /li> tags because I use lists for all my navigation controls. You can then place these links in any type of list, allowing freedom with the list's class/style.)
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<NavigationLink>>">
<% foreach(NavigationLink n in Model){ %>
<li>
<a href="<%= string.IsNullOrEmpty(n.RouteName) ? Url.RouteUrl(Routes) : Url.RouteUrl(RouteName) %>">
<%= n.Text %>
</a>
</li>
<% } %>
Menus.cs (Some examples)
public static Menus
{
public static List<NavigationLink> MainMenu()
{
List<NavigationLink> links = new List<NavigationLink>();
links.Add(new NavigationLink{
Text = "Home",
Routes = new RouteValueDictionary(new { action="Index", controller="Home" })
});
return links;
}
public static List<NavigationLink> UserMenu()
{
List<NavigationLink> links = new List<NavigationLink>();
links.Add(new NavigationLink{
Text = "Messages",
Routes = new RouteValueDictionary(new { action="Messages", controller="UserCP" })
});
links.Add(new NavigationLink{
Text = "Account",
Routes = new RouteValueDictionary(new { action="Account", controller="UserCP" })
});
return links;
}
}
Now that you have everything setup, calling these functions is simple:
In your view file (.aspx/.ascx)
<ul class="horizontal_menu">
<% Html.RenderPartial("NavigationLink", MyMvcApplication1.Menus.MainMenu()) %>
</ul>
Having it setup like this allows for different partial views to be created to render out lists of navigation links in different ways and would require that you only build the partial and call it.
Related
In the new release of ASP CORE 2.0 last month, they introduced Razor Pages, this has me in a loop since the old controller & the model frpm MVC are missing from ASP CORE 2 Razor pages.
My understanding from this page is that we get default binding of the properties using a [BindProperty] attribute outside the Action/Method!!!!, this is because its moved to an MVVM framework as opposed to MVC framework.
Question: While trying to rewrite the traditional actions, since there are no controllers, how to move to the code to the new RazorPages MVVM framework, i.e. and where and how to bind the properties, and actions/handlers?
Since the properties are not in the signature, how does the action/handler know which properties were passed to it from the View/Razor Page?
What is the PageModel?
public class CreateModel : PageModel // what is this pagemodel, is it new or the same old model?
{
private readonly AppDbContext _db;
public CreateModel(AppDbContext db)
{
_db = db;
}
[BindProperty]
public Customer Customer { get; set; } // why is the property outside?
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
}
Razor pages is from my understanding pretty much a replacement for the old asp.net forms where you simply have a page with logic. A bit like how php does things.
If you create a page, let's say Pages/Index2.cshtml you should also create (or it may be created for you in Visual Studio) a "code-behind" file called Pages/Index2.cshtml.cs for example.
// The page file
#page
#using RazorPages
#model IndexModel2
<h2>Separate page model</h2>
<p>
#Model.Message
</p>
// The code-behind file
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
namespace RazorPages
{
public class IndexModel2 : PageModel
{
public string Message { get; private set; } = "PageModel in C#";
public void OnGet()
{
Message += $" Server time is { DateTime.Now }";
}
}
}
You can still have models and initialize them in the code-behind file. But if you want controllers, I would suggest you do not use razor pages and just use the classical mvc. You can create a new project with it, just don't choose razor pages from the template. You certainly do not need to create a razor pages project. It is just an option. I personally do not really use it since I think is prone for one to repeat code since every code-behind file is just valid for one page afaik.
What is the PageModel?
A pagemodel is simply the code-behind file that does server side logic for your specific page.
I am not sure exactly what you are asking for, you bind models like any other razor page and properties in the code-behind class. The model is the code-behind file in my example.
how does the action/handler know which properties were passed to it from the View/Razor Page?
The action handler knows it by you specifying it in the razor page:
<input asp-for="Customer.Name" />
Please read more about Razor Pages here:
https://learn.microsoft.com/en-us/aspnet/core/mvc/razor-pages/?tabs=visual-studio
I would like to know how I can render a custom header (based on customerid) and a side menu (again, based on the customerid) + the main content.
On a search page, I have a list of customers. When I select a customer, it will take me to another page passing the id of that customer (/controller/action/id). However, on this new page, I would like to list customer details on the header (name, email, phone, etc), on the side menu, I would like to list the pets that this customer owns (dog1, cat2, etc) and on the main content I want to show details of the first pet on the list (side menu). The list of pets on the side menu should be links. For instance, the dog1 should be a link to its own details shown on the main content. Cat2 should be a link to show details on the main content, etc
I just don't know how I am going to pass the id to the header, side menu and content page, all at once!
I have seen the mvcMusicStore where we have a list of the genres on the left menu, but that does not change! In my case, the side menu should change according to the customer selected on the previous page! Make sense?
You have multiple choices. At first, you can use partial views:
in layout.cshtml [or in your usecase, search page]:
#Html.Partial(your-partial-view-name)
view: headerPartial.cshtml as an example:
#if(User.Identity.IsAuthenticated) {
<span>Welcome</span> #User.Identity.Name
} else {
<span>Login or Register</span>
}
This is the base logic. For your situation, you need something like this. But, if you have to load some data from for example database, I would suggest you using partial actions:
// in layout or search or any page you want to render partial views:
// For example you have a controller named MyPartials:
// Put this line where you want to render header:
#Html.Action("HeaderInfo", "MyPartials")
and action:
public class MyPartialsController : Controller {
public ActionResult HeaderInfo() {
var model = retrive header needed info from db
return Partial(model);
}
}
Let me know more about your problem in details, to suggest you a good solution
UPDATE:
Suppose:
Controller: CustomerController
Action: SeletedCustomer
Solution:
1. Models:
public class HeaderInfoModel { } // contains header data: name, email, phone, etc
public class SideInfoModel { } // dog1, cat2, etc
public class MainContentModel { } // holding main content data
// bringing all together
public class CustomerModel {
public HeaderInfoModel HeaderInfo { get; set; }
public SideInfoModel SideInfo { get; set; }
public MainContentModel MainContent { get; set; }
}
2. Controller
public class CustomerController : Controller {
public ActionResult SeletedCustomer(int id) {
// use id and retrieve data you want, from db or anywhere else you want
var model = new CustomerModel {
HeaderInfo = SomeMethodToFetchHeaderData(id),
SideInfo = SomeMethodToFetchSideData(id),
MainContent = SomeMethodToFetchMainContent(id)
};
return View(model);
}
}
3. Main View: Customer/SeletedCustomer.cshtml
#model CustomerModel
<div id="header">
<!-- put this where you want to render header -->
#Html.DisplayFor(model => model.HeaderInfo)
</div>
<div id="side">
<!-- put this where you want to render side -->
#Html.DisplayFor(model => model.SideInfo)
</div>
<div id="main">
<!-- put this where you want to render main content -->
#Html.DisplayFor(model => model.MainContent)
</div>
4. In your situation, using Display Templates is the easiest way. Create a folder named DisplayTemplates in one of these places: ~/Views/Customer/ or ~/Views/Shared. It depends on where and how you want to use provided models. Again, in your case, I suggest create this: ~/Views/Customer/DisplayTemplates/. And then add some views in that folder, which are described below:
4.1. Create HeaderInfoModel.cshtml in ~/Views/Customer/DisplayTemplates/:
#model HeaderInfoModel
<div>
implement your model presentation, I mean, show the data how you want
</div>
4.2. Create SideInfoModel.cshtml in ~/Views/Customer/DisplayTemplates/:
#model SideInfoModel
<div>
implement your model presentation, I mean, show the data how you want
</div>
4.3. Create MainContentModel.cshtml in ~/Views/Customer/DisplayTemplates/:
#model MainContentModel
<div>
implement your model presentation, I mean, show the data how you want
</div>
That is it. Done! With your situation, the best way I can suggest is that. However, you have multiple choices; It's completely depended on your project.
You probably need to use some kind of partial view with condition like it shown below inside razor
if(this is correct page && CustomerID is available) { show the view }
You can pass parameters in ViewBag and use them inside your view.
If you are new to ASP.NET MVC, here are some cool tutorials http://www.asp.net/mvc.
And here is tutorial of building partial views http://mvc4beginner.com/Tutorial/MVC-Partial-Views.html for beginners.
Hope this helps
We're working with ASP.Net MVC and Google Publisher Tags (GPT).
GPT requires that you create javascript "slots" in the <head> and some html+script that goes into the <body>. The key dependency here is that in both places is an id that must match. So the head will contain some Javascript that includes something like:
<head>
<script>
...
DefineSlot('div-gpt-ad-123456789-0', 'foo', 'bar')
...
</script></head>
and
<body>
...
<div id='div-gpt-ad-123456789-0'>
<script>
...
Display('div-gpt-ad-123456789-0')
...
</script>
</div>
...
How can we manage the dependencies between these 2 pieces code? The critical piece is that the id of both parts must match.
We want to use MVC to create these pieces of code dynamically. So in any view, partial or layout, I will be able to add a helper call that might look like:
#Html.CreateAd(size, "foo", "bar")
#Html.CreateAd can be called anywhere in a view, partial view, layout, or nested layout.
How do you use ASP.Net MVC to program the code that goes into <head>?
Any suggestions are appreciated. I'm just looking for direction, not a full blown solution.
Many Thanks.
You have a few different ways to do this.
You can add the id's to the ViewData or a base viewmodel.
Then OnActionExecuting or OnActionExecuted in a base controller or via actionfilters, you can add your data to whichever place you prefer to. If you need examples for this, please leave a comment on this answer.
Then, your helpers (one for each section) you can read from one of the 2 sources you decided on. I have gone both routes. If all of your pages are going to have an ad, then I would lean towards the base ViewModel. If it's more of a rare occurrence ViewData would be more appropriate.
To access the viewdata within an htmlHelper Extension method:
public static class HtmlExtension
{
public static MvcHtmlString RenderAdHead(this HtmlHelper h)
{
h.ViewContext.ViewData.Model // a test and cast here
h.ViewContext.ViewData["AdIdentifier"] // test for null and cast here
string tags = String.Empty;
//build up string to resemble your script/html tags using either of the
//sources above, so long as either source is not empty.
return new HtmlMvcString(tags);
}
}
And some code for a filter:
public class AdvertisingFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
List<String> idList = null; // list of string might not be adequate. depends on your implementation
//read advertising ids from your datastore or wherever you have.
filterContext.Controller.ViewData["advertisingFilter"] = idList;
}
}
The basecontroller is pretty much the same, instead you have the controllercontext directly. You just have to be sure all your controllers inherit from them.
I'm an experienced .NET programmer, but I'm new to this whole web programming thing. My ASP.NET MVC website has a global layout that includes some content (menu links at the top of the page) that I want to hide under conditions that are detected dynamically by controller code.
My inclination -- the simple approach that uses the tools I've learned about thus far -- is to shove a Boolean HideGlobal value into the ViewBag, and to put the global markup in _Layout.cshtml that I want to hide inside of an #if (ViewBag.HideGlobal){} block.
I just want to know if this is the "proper" way to do it, or is there some Razor magic that I should be using for reasons that are not yet evident to me?
I dislike using the view model of the action outside of the view returned by the action. Using base view model for this scenario feels very clunky.
I believe it's cleaner and more obvious to just use a separate (child) action that contains the logic for specifying how the global menu should be displayed. This action returns the global menu view. Call that action from your layout page.
Or you can create an action for the entire header where the menu's state is determined -- or do an if/else to render a partial view of the global menu.
The example below encapsulates the needs of a header/global menu and offers a future proof way of changing your header/menu with minimal effect on your code infrastructure (base view model).
~/Controllers/LayoutController.cs
public class LayoutController : Controller
{
[ChildActionOnly]
public ActionResult Header()
{
var model = new HeaderViewModel();
model.ShowGlobalMenu = ShowGobalMenu();
return View(model);
}
}
~/Views/Layout/Header.cshtml
#model HeaderViewModel
#{
Layout = "";
}
<header>
Home
#if(Model.ShowGlobalMenu)
{
<ul>
<li>Link</li>
<li>Link</li>
<li>Link</li>
<li>Link</li>
</ul>
}
</header>
~/Views/Shared/_Layout.cshtml
<html>
<body>
#Html.Action("Header", "Layout")
<p>Stuff</p>
</body>
</body>
What you've described (putting a bool into the ViewBag) will work fine. But personally, I like the strongly-typed model experience, so when I want to have UI logic like what you're describing in the master/layout page (which is pretty much always), I prefer to put those flags into a base model that my other models inherit from.
public class BaseModel
{
public bool HideGlobal { get; set; }
}
And at the top of the _Layout.cshtml page, I specify that I'm expecting a BaseModel:
#model Company.Project.BaseModel
Other views can, of course, require other model types, but if they're using this layout, those model types should derive from BaseModel.
Finally, when I want to check the flag, rather than having a mysterious, undocumented field in the ViewBag, I've got the lovely intellisense and feel-good strongly-typed member of the model:
#if (!Model.HideGlobal)
{
<div>...</div>
}
Edit: I should probably add that it's often nice to also have a base controller whose job it is to populate the fields in BaseModel. Mine usually look like this:
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
if (result != null)
{
var baseModel = result.Model as BaseModel;
if (baseModel != null)
{
//Set HideGlobal and other BaseModel properties here
}
}
}
}
First answer here, be gentle :-)
This type of thing is usually based on user permissions which would be much better in an Action Filter instead of the base controller.
Yes you would have to use the ViewData or ViewBag but it's really not part of the view/model it's higher up in your _layout.cshtml
Action Filter
public class UserAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext filterContext ) {
var hasAccessToMenu = /* Code */;
filterContext.Controller.ViewData["GlobalVisible"] = hasAccessToMenu;
}
}
View (_layout.cshtml)
#if(ViewData["GlobalVisible"])
{
<ul>
<li>Link</li>
<li>Link</li>
<li>Link</li>
<li>Link</li>
</ul>
}
This gives you the advantage of separating this logic out of the controller and because it's global that's important.
Also I've used ViewData in my example but using ViewBag is just as good.
I'm trying to build a global menu into my ASP.NET MVC site.master, and I was wondering how I could go about accessing the Application Settings property from the site.master markup? Previously I probably would have instantiated a config object from my site.master's code-behind and then set a public property. But now I'm scratching my head...must need more coffee.
UPDATED with answer code
Added a string setting to the application propererties called baseurl and gave it a value of "http://mysite.com"
Made a model class of GlobalMenu.cs
public class GlobalMenu
{
private string _baseurl;
public string baseurl
{
get { return _baseurl; }
set
{
_baseurl = value;
}
}
}
Created a base controller class named BaseController and inherited from Controller, and overroad OnActionExecuted thusly:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
string baseurl = Properties.Settings.Default.baseurl;
GlobalMenu menumodel = new GlobalMenu();
menumodel.baseurl = baseurl;
ViewData["menudata"] = menumodel;
base.OnActionExecuted(filterContext);
}
Created a partial view called ViewGlobalMenu in the Shared folder that was strongly typed to GlobalMenu that looks like this...but with more stuff obviously:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyApp.Web.Models.GlobalMenu>" %>
Finally in Site.Master I added this to where I wanted the menu to show:
<%Html.RenderPartial("ViewGlobalMenu", (MyApp.Web.Models.GlobalMenu)ViewData["menudata"]); %>
Here's the strategy that I would probably use. Create a base controller from which your other controller's will derive and have it derive from Controller. Override the ActionExecuted method in the base controller and have it access the application settings (and probably cache them). Generate ViewData for your menu as strongly-typed menu model class assigned to a particular key in the ViewData. You only need to provide the model to actions that are returning a ViewResult (and, perhaps, PartialViewResults).
Create a strongly-typed partial view that implements the global menu markup using the menu model class. Include this in the MasterPage definition via RenderPartial. Pass the ViewData item corresponding to the key as the Model to the partial view so that you can use the model's properties in your menu.
This should do the trick from within the View -->
#System.Configuration.ConfigurationManager.AppSettings["AppSetting"]