Route/view binding issue with MVC3/Razor - c#

In my MVC3 application, I have an action that routes to a custom action depending on an object selected.
public ActionResult SearchCityState(string city, string state, string searchTerm)
{
city = Server.HtmlEncode(city);
state = Server.HtmlEncode(state);
searchTerm = Server.HtmlEncode(searchTerm);
// now build the search object
...
return DoSearch(sourceRequestObject);
}
public ActionResult SearchState(string state, string searchTerm)
{
state = Server.HtmlEncode(state);
searchTerm = Server.HtmlEncode(searchTerm);
// now build the search object
...
return DoSearch(sourceRequestObject);
}
Those two methods do a bit of work in populating an object and calling the following DoSearch() method in the class and are selected based on some logic:
public ActionResult DoSearch(FeederService.SearchRequestObject sourceRequestObject)
{
...
var model = new MyAppMVC.Models.ResultsModel();
var page = model.GetData(sourceRequestObject);
return View(page);
}
Here's my model class:
public class ResultsPage
{
public DataSet dsResults { get; set; }
public Int32 actualNumberOfResults { get; set; }
public int numberOfResultsReturned { get; set; }
}
public class ResultsModel
{
...
public ResultsPage GetData(FeederService.SearchRequestObject sourceRequestObject)
{
var page = new ResultsPage();
...
page.dsResults = myWcfFeederClient.GetData(sourceRequestObject);
if (page.dsResults != null)
{
page.actualNumberOfResults = Convert.ToInt32(page.dsResults.Tables[1].Rows[0]["ActualNumberOfResults"].ToString());
page.numberOfResultsReturned = Convert.ToInt16(page.dsResults.Tables[1].Rows[0]["NumberOfResultsReturned"].ToString());
}
return page;
}
}
I have a view defined in /Results/SearchResults.cshtml that I want to route all requests to, as the output will be the same for all
The issue is that the initially selected action name is the default selected view. ie. if SearchCityState() is called, the following exception is thrown:
The view 'SearchCityState' or its
master was not found or no view engine
supports the searched locations. The
following locations were searched:
~/Views/Results/SearchCityState.aspx
~/Views/Results/SearchCityState.ascx
~/Views/Shared/SearchCityState.aspx
~/Views/Shared/SearchCityState.ascx
~/Views/Results/SearchCityState.cshtml
~/Views/Results/SearchCityState.vbhtml
~/Views/Shared/SearchCityState.cshtml
~/Views/Shared/SearchCityState.vbhtml
... and similar for SearchState(). I'm familiar with this issue, but I can't recall how to route all requests to that one view.
Thanks.
UPDATE
Here are the routes I have defined:
routes.MapRoute(name: "CityHomePage", url: "{city}-{state}", defaults: new { controller = "Home", action = "GeoHomePage" });
routes.MapRoute(name: "CityStateResults", url: "{city}-{state}/{searchTerm}", defaults: new { controller = "Results", action = "SearchCityState" });
... and off of a link defined as:
My CityState Link
I'm ending up with the following error:
The view 'SearchCityState' or its
master was not found or no view engine
supports the searched locations. The
following locations were searched:
~/Views/Results/SearchCityState.aspx
~/Views/Results/SearchCityState.ascx
~/Views/Shared/SearchCityState.aspx
~/Views/Shared/SearchCityState.ascx
~/Views/Results/SearchCityState.cshtml
~/Views/Results/SearchCityState.vbhtml
~/Views/Shared/SearchCityState.cshtml
~/Views/Shared/SearchCityState.vbhtml

Use another overload of the View() method, which takes the view name as the 1st parameter:
public ActionResult DoSearch(FeederService.SearchRequestObject sourceRequestObject)
{
...
var model = new MyAppMVC.Models.ResultsModel();
var page = model.GetData(sourceRequestObject);
return View("SearchResults", page);
}
(The MSDN article isn't helpful, but the answer doesn't feel complete)

Related

How can I create a global menu for my application whith its items pulled from a database?

I need to put couple drop down menus on the upper right hand side of my application. These menus need to appears on every page where that layout is used.
The only problem is that items of the menu are pulled from a database.
Usually I would pass the list to the model like so
public ActionResult Clients()
{
using (SomeContext db = new SomeContext())
{
var clients = db.Database.SqlQuery<Client>("SELECT * FROM clients").ToList();
return View(clients);
}
}
But, I am not sure how to do the same thing without having to write the same code for every view. I want to only write this code below once and not worry about having to write the same code for every view.
What is the right way to have a global drop down menu for my application?
I prefer to use an controller to render my menu. This provides caching, reuse and logic for a menu (like showing or not showing a menu based on roles/claims). You can read the complete article by Phil Haacked - Html.RenderAction and Html.Action, excerpt below.
c#
public class MenusController {
[ChildActionOnly]
public ActionResult MainMenu() {
var menu = GetMenuFromSomewhere();
return PartialView(menu);
}
}
Html:
<html>
<head><title></title></head>
<body>
#Html.Action("MainMenu", "Menus")
<h1>Welcome to the Index View</h1>
</body>
</html>
You can create an action filter to do this.
public class LoadMenu : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var vb = filterContext.Controller.ViewBag;
var menu = new List<MenuItem>();
//I am hard coding to 2 items here. You may read it from your db table
menu.Add(new MenuItem() { Text = "Home", TargetUrl = "Home/Index" });
menu.Add(new MenuItem() { Text = "Careers", TargetUrl = "Home/Careers" });
vb.Menus = menu;
}
}
Assuming you have a class called MenuItem
public class MenuItem
{
public string Text { set; get; }
public string TargetUrl { set; get; }
public List<MenuItem> Childs { set; get; }
public MenuItem()
{
this.Childs = new List<MenuItem>();
}
}
Now, if you want this in every page, just register it globally. You can do this in the RegisterRoutes method in RouteConfig class
public static void RegisterRoutes(RouteCollection routes)
{
//Existing route definitions goes here
GlobalFilters.Filters.Add(new LoadMenu());
}
Now in your Layout file, read the ViewBag item called Menus and build the menu markup as needed.
#{
var menu = ViewBag.Menus as List<MenuItem>;
if (menu != null)
{
foreach (var m in menu)
{
<li>#m.Text</li>
}
}
}
You may update the above code to render Childs as needed.

How to intercept a Url to dynamically change the routing

I am looking to do something like:
For categories where the Controller will be CategoryController
www.mysite.com/some-category
www.mysite.com/some-category/sub-category
www.mysite.com/some-category/sub-category/another //This could go on ..
The problem is that: www.mysite.com/some-product needs to point to a ProductController. Normally this would map to the same controller.
So, how can I intercept the routing so I can check if the parameter is a Category or Product and route accordingly.
I am trying to avoid having something like www.mysite.com/category/some-category or www.mysite.com/product/some-product as I feel it will perform better on the SEO side. When I can intercept the routing, I'll forward to a product / category based on some rules that look at slugs for each etc.
You could write a custom route to serve this purpose:
public class CategoriesRoute: Route
{
public CategoriesRoute()
: base("{*categories}", new MvcRouteHandler())
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
string categories = rd.Values["categories"] as string;
if (string.IsNullOrEmpty(categories) || !categories.StartsWith("some-", StringComparison.InvariantCultureIgnoreCase))
{
// The url doesn't start with some- as per our requirement =>
// we have no match for this route
return null;
}
string[] parts = categories.Split('/');
// for each of the parts go hit your categoryService to determine whether
// this is a category slug or something else and return accordingly
if (!AreValidCategories(parts))
{
// The AreValidCategories custom method indicated that the route contained
// some parts which are not categories => we have no match for this route
return null;
}
// At this stage we know that all the parts of the url are valid categories =>
// we have a match for this route and we can pass the categories to the action
rd.Values["controller"] = "Category";
rd.Values["action"] = "Index";
rd.Values["categories"] = parts;
return rd;
}
}
that will be registered like that:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("CategoriesRoute", new CategoriesRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
and then you can have the corresponding controller:
public class CategoryController: Controller
{
public ActionResult Index(string[] categories)
{
... The categories action argument will contain a list of the provided categories
in the url
}
}

Change URL Routing by overriding GenericPathRoute.cs in Plugin nopCommerce 3.3

I am trying to create a plugin which will override the TopicsDetails.cshtml page. I added a route like this:
routes.MapRoute("Nop.Plugin.Other.CustomTopic.ViewCustomTopic", "{SeName}",
new { controller = "CustomTopic", action = "TopicDetails", SeName = UrlParameter.Optional },
new[] { "Nop.Plugin.Other.CustomTopic.Controllers" });
This is getting all the {SeName} to my CustomTopicController .Even the products SeName.
If I add this instead of the older one:
routes.MapRoute("Nop.Plugin.Other.CustomTopic.ViewCustomTopic",
new { controller = "CustomTopic", action = "TopicDetails" },
new[] { "Nop.Plugin.Other.CustomTopic.Controllers" });
I get an error because the TopicDetails(int itemId) Action receives an integer which is not provided as we know that GenericPathRoutes.cs Provides that integer.
How can I override the Rules of GenericPathRoutes.cs to do it so that only the topic SeName would hit my Controller or is there other way to do that kind of work or is it even possible to do?
Recently i wrote an article which shows how to override localized route and i used the TopicDetails view as an example. There article is here.
Inshort, your route provider shall look like this;
public class RouteProvider : IRouteProvider
{
private const string NAMESPACES = "Nop.Plugin.Misc.Custom.Controllers";
private const string CONTROLLER = "MiscCustom";
public void RegisterRoutes(RouteCollection routes)
{
//Public Override
routes.MapGenericPathRoute("Plugin.Misc.Custom.GenericUrl",
"{generic_se_name}",
new { controller = "Common", action = "GenericUrl" },
new[] { NAMESPACES });
}
public int Priority
{
get { return Int32.Max; }
}
}
I am not sure whether i understand your question
can you try it by adding
id = UrlParameter.Optional
here any id parameter is optional it wont throw that error

connecting controller with model to display results in view page

So i have this aps.net mvc project in which i created a service layer, model views, controller, and a view page. But i am having trouble displaying my results to the view page. I am starting this would by passing in a specific linq statement in the service layer so i should be able to return it to show up on the view. Here is what i have:
Service:
public IEnumerable<RoleUser> GetUsers(int sectionID)
{
var _role = DataConnection.GetRole<RoleUser>(9, r => new RoleUser
{
Name = RoleColumnMap.Name(r),
Email = RoleColumnMap.Email(r)
}, resultsPerPage: 20, pageNumber: 1);
return _role;
}
Models:
public partial class Role
{
public RoleView()
{
this.Users = new HashSet<RoleUser>();
}
public ICollection<RoleUser> Users { get; set; }
}
public class RoleUser
{
public string Name { get; set; }
public string Email { get; set; }
}
Controller:
public ActionResult RoleUser(RoleView rvw)
{
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
View:
<div>
<span>#Model.Name</span>
</div>
I am not sure what i am missing or doing wrong but any tips will be great. I basically want to return the results from the linq statement i am testing to see that the connection is correct and functionality is there before enhancing. Thanks...
Well, if I were to go off the code you've provided I would say that I'm unsure how this compiles:
public partial class Role
{
public RoleView()
{
this.Users = new HashSet<RoleUser>();
}
public ICollection<RoleUser> Users { get; set; }
}
it feels like that should be:
public partial class RoleView
and then I would say that at the top of your view you're missing this:
#model NamespaceToClass.RoleView
and then I would say you're not going to be able to issue this:
#Model.Name
because RoleUser isn't your model. You're going to need to loop through the users:
#foreach (RoleUser ru in Model.Users)
and then inside that loop you can build some HTML with this:
ru.Name
but I would also question your controller. Right now it's receiving a model to return that model. There is some code missing here but generally speaking, inside the method:
public ActionResult RoleUser(RoleView rvw)
you would actually go get the data, construct the model, and then return that:
var users = serviceLayer.GetUsers(...);
// now construct the RoleView model
var model = ...
return View(model);
Based off of our conversation you currently have something like this in your controller:
public ActionResult View(int id)
{
// get the menu from the cache, by Id
ViewBag.SideBarMenu = SideMenuManager.GetRootMenu(id);
return View();
}
public ActionResult RoleUser(RoleView rvw)
{
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
but that really needs to look like this:
public ActionResult View(int id)
{
// get the menu from the cache, by Id
ViewBag.SideBarMenu = SideMenuManager.GetRootMenu(id);
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
because you're launching this page from the sidebar which is hitting this action because you're passing the id in the URL. You don't even need the other action.

How to call multiple actions in View in ASP.NET MVC?

Problem is:
I am using a textbox to get a string q and want to pass it to 3 different actions in search controller. i.e. action1(string q), action2(string q) and so on
Now syntax of my action:
public ActionResult action1(string q)
{
var mydata = from p in fab //LINQ logic
select new action1class
{ data1=p //assignment };
return View("_partialAction1", mydata);
}
Similarly there are two other actions.
I am using 3 different actions because my LINQ logic gets data from 3 different sources so there different mydata needs to be created.
My problem is: I am trying that when I click on 'search' Button of textbox then all the 3 actions should run and generate partial view one below other in some <div id="action1"> tags.
I tried to use ajax.BeginForm but it can only call one action at a time
#using (Ajax.BeginForm("action1", "Search", new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "action1",
LoadingElementId="progress"
}))
Also I tried to use ViewModel but the problem is that I was unable to pass a bigger model to the view along with these mydata kind of data obtained in LINQ's in the action. I have no clear idea of how to use viewmodel in this case.
Is the approach that I am using correct? Or can there be any other way? I want to show result of all actions with button click.
There are two types of actions are in MVC framework. The first ones are the main actions and they are invoked from the browser one at a time. The second type are called as Child Actions and these actions can't be invoked from the browser but from the views returned by the main actions. Multiple child actions can be called under a main action. So you have to look into child actions whether they help or not.
Ex.
// main action that returns a view
public ViewResult Index()
{
var model = ...
return View(model);
}
// couple of child actions each returns a partial view
// which will be called from the index view
[ChildActionOnly]
public PartialViewResult ChildAction1()
{
var model = ...
return PartialView(model);
}
[ChildActionOnly]
public PartialViewResult ChildAction2()
{
var model = ...
return PartialView(model);
}
// index view
Index.cshtml
#model ...
#Html.Action("ChildAction1");
#Html.Action("ChildAction2");
...
http://msdn.microsoft.com/en-us/library/ee839451.aspx
You can only have one action per request. If you want to have 3 different partial views for a singular click, you will need to construct a layout page that includes the 3 partial views how you want them and make sure that your action receives the proper parameters to perform all of the partial view rendering.
Why not pass the ViewModel to the partialViews. Make sure you have different properties in the ViewModel to hold the PartialView Specific data plus the search text. Here is an example:
Model
public class Product
{
public string Name { get; set; }
public string Type { get; set; }
public string Class { get; set; }
}
ViewModel
public class ProductSearch
{
public ProductSearch()
{
q = string.Empty;
Product1 = new Product();
Product2 = new Product();
}
public string q { get; set; }
public Product Product1 { get; set; }
public Product Product2 { get; set; }
}
_Partial1.cshtml
#model Test1.Models.ProductSearch
<div>Product1</div>
#Html.TextBoxFor(a => a.Product1.Name)
_Partial2.cshtml
#model Test1.Models.ProductSearch
<div>Product2</div>
#Html.TextBoxFor(a => a.Product2.Name)
ActualView.cshtml
#model Test1.Models.ProductSearch
#{
ViewBag.Title = "ActualView";
}
<h2>ActualView</h2>
#using (Html.BeginForm())
{
#:SearchText
#Html.TextBoxFor(m => m.q)
Html.RenderAction("_Partial1", Model);
Html.RenderAction("_Partial2", Model);
<input type="submit" runat="server" id="btnSubmit" />
}
Temp Data (you will be getting it from DB/ any other source)
private List<Product> ProductsToSearch()
{
return new List<Product>() { new Product() { Name = "Product One", Class = "A", Type = "High" }, new Product() { Name = "Product Two", Class = "A", Type = "Low" }, new Product() { Name = "Product Three", Class = "B", Type = "High" } };
}
Controller Actions
public ActionResult _Partial1(ProductSearch search)
{
Product Product1 = ProductsToSearch().Where(a => a.Class.Equals(search.q) && a.Type.Equals("High")).SingleOrDefault();
search.Product1 = Product1;
return PartialView(search);
}
public ActionResult _Partial2(ProductSearch search)
{
Product Product2 = ProductsToSearch().Where(a => a.Class.Equals(search.q) && a.Type.Equals("Low")).SingleOrDefault();
search.Product2 = Product2;
return PartialView(search);
}
[HttpPost]
public ActionResult ActualView(ProductSearch search)
{
return View(search);
}
public ActionResult ActualView()
{
ProductSearch search = new ProductSearch();
return View(search);
}
Now if you enter 'A' for SearchText and hit Submit Query you will get two different results (basically common search text is used and based on the search query in each partial view it has generated different results)

Categories

Resources