Custom MVC Routing - asp.net-mvc

Okie, I am trying to finish a product dispkay for a client, in my code I have this
#foreach (var item in Model)
{
<div class="itemcontainer">
<p class="button">#Html.ActionLink(item.Category, item.Category) (#item.Count)</p>
</div>
}
Which gives me the link (URL) of Products/Categories, now what do I need to get to my ultimate goal of (for example) Products/Braceletsss. Do I have to write a custom route, if so can someone show me an example, I'm still trying to get my head around this.
**EDIT*
I can provide more code if it's needed :)

[HttpGet, Route("products/{categoryName}")]
public IActionResult GetProductsByCategoryName(string categoryName) {
... code to retrieve products by category name
The above is one way to do it, the way that I prefer at least. When you access the route /products/nine-millimeter-handguns, then in your action, the categoryName variable will have the value nine-millimeter-handguns. You can then use that string value to look up all of the products in that category and return them to the client.
The other way to do it is in your global route config in Startup.cs. If you do it this way, you don't need the [Route] attribute on the action method:
public void Configure(IApplicationBuilder app) {
...
app.UseMvc(routes => {
routes.MapRoute(null, "products/{categoryName}", new {
controller = "Products", action = "GetProductsByCategoryName"
});
});
}
I prefer the former attribute approach because it keeps the routes closer to the controllers & actions that they map to. But both will accomplish the same thing.
In order to render a link to this route from a view, you would pass in the categoryName to the ActionLink html helper method:
#Html.ActionLink(item.Category, item.Category, new {
categoryName = "nine-millimeter-handguns"
})

Related

ASP.NET MVC Clean way to inject partial view from action

I have an app with many widgets and their content depends on the user requesting specific route. Simply put: if widget action is requested, its content must be rendered, otherwise it's empty. Consider routes/actions like this:
~/MyApp/Index -> without model; app HTML, without any widgets
~/MyApp/Foo/{id} -> uses FooModel; if ModelState is valid, returns
Index HTML with injected partial view of Foo's widget to div#foo;
otherwise redirects to Index.
~/MyApp/Bar/{id} -> same as Foo, but different model and widget
My foo action :
public ActionResult Foo(string id) {
if (ModelState.IsValid) {
var response = FooService.GetData(id);
// Inject Foo widget to Index
}
return RedirectToAction("Index");
}
I know that it is possible to use ViewBag or other means to send variables and using the condition to decide whether to render partial view or not. But... there should be a better way to do this, right?
I use MVC's Html.RenderActionResult when I want to build shared views with non-trivial binding logic (calling the database, composing complex objects, etc). The binding logic for each widget is contained in a PartialViewResult method, which is called from the *.cshtml file using Html.RenderAction().
ContentController:
public ActionResult Index(int id)
{
var indexViewModel = new IndexViewModel
{
Id = id,
Title = "My Title",
SubHeader = "Wow its 2016"
};
return View(indexViewModel);
}
public PartialViewResult PopularContent(int id)
{
var popularContentViewModel = new List<PopularContentViewModel>();
// query by id to get popular content items
return PartialView("_PopularContent", popularContentViewModel);
}
public PartialViewResult Widget2(int id)
{
return PartialView("_Widget2Partial");
}
Index.cshtml:
#model StackOverflow.RenderAction.ViewModels.IndexViewModel
<h1>#Model.Title</h1>
<h2>#Model.SubHeader</h2>
--RenderAction will call out to the specified route.
--Note the use of the Id parameter from the viewmodel.
#{Html.RenderAction("PopularContent", "Content", new {Model.Id});}
ASP.NET MVC Attribute Routing could a be a nice solution for this:
In your controller:
public class WidgetController : Controller
{
[Route("myapp/foowidget", Name = "FooWidget")]
public ActionResult FooWidget()
{
//create any model and return any view or partial or redirect
}
[Route("myapp/boowidget/{id:int}", Name = "BooWidget")]
public ActionResult BooWidget(int id)
{
//create any model and return any view or partial or redirect
}
}
And then in a View, you can call the Route by name:
#Url.RouteUrl("FooWidget")
or
#Url.RouteUrl("BooWidget")
or
#Html.RenderPartial("FooWidget")
#Url.RouteUrl("BooWidget") will render or concatenate the id that is in current url, if url is /myapp/something/id, because of your Route attribute definition: "myapp/boowidget/{id:int}". In fact #Url.RouteUrl("BooWidget") might extract the id from any current url of the format /controllerName/action/id, though you will have to test for sure.
And notice how you can have a separation of concerns with your WidgetController and your url Routes are not dependent on that controller's name in any way. That is a nice feature of Attribute Routing, you can declare custom routes as well as organize your controllers and break from nameing convention dependency of a controllerName being part of the url controllerName/action a user sees in their browser.
In regards to Html.RenderPartial, I am not sure if RenderPartial "connects" or will be able to route to your RouteName like "FooWidget". If it does great.
If not your solution is this:
public class WidgetController : Controller
{
public ActionResult FooWidget()
{
//model, you choose, return a partial
}
public ActionResult RedirectUser()
{
//do a redirect
}
public ActionResult BooWidget()
{
//any model, any partial
}
public ActionResult BooWidget(int id)
{
//any model, any partial
}
}
Each method in your controller is single purpose, has a distinct signature and does one thing, no conditions to pass in and no decisions required.

MVC RouteConfig Setting Controller and Action attributes as parameters

I'm currently working on a course application site in MVC. I'm relatively new to MVC and I'm trying to get to grips with the RouteConfig file to achieve the aims of the information architecture and to avoid duplication.
I see this as an opportunity to have just three Controllers/views to handle the bulk of the logic:
View 1. Region/Country Select screen
View 2. Course Select screen
View 3. Course Detail and Application screen
The information architecture should work as follows:
{region}/{country}/{course}
The user selects a country/state where their course will operate from, the course country can exist in one of three geographic areas: AMER, EMEA and APAC
The user selects on of the courses available in their country/state.
~/AMER/USA/SignLanguage
~/EMEA/GBR/FirstAid
~/APAC/AUS/EmploymentLaw
Each of the values for, region, country/state and course are db driven
The user view the course details, and applies for a class on the course.
Looking at the Default MapRoute and searching high and low, I'm struggling to find a way to introduce dynamic control and action source (operating as parameters), ensuring that I don't have to build three controls to handle region and a multitude of actions to accommodate each country being handled.
As the course is effectively an id anyway I'm pretty confident that that isn't a big issue.
I figure I could do something like this to handle the controller logic, but I think I would still have to produce several Controllers even in this scenario as well as the hundreds of actions.
routes.MapRoute(
name: "Courses",
url: "{controller}/{action}/{id}",
defaults: new {
controller = "Region",
action = "Index",
id = UrlParameter.Optional
}
constraints: new {
controller = #"^(AMER|EMEA|APAC).+/i"
});
What is the best way to resolve this issue?
You are close, what about doing this for the route:
routes.MapRoute(
name: "Default",
url: "{region}/{country}/{course}",
defaults: new { controller = "Course", action = "Detail" }
);
And Controller:
public class CourseController : Controller
{
// GET: Course
public ActionResult Detail(string region, string country, string course)
{
//TODO: Validation
var data = new Data
{
Region = region,
Country = country,
Course = course
};
return View(data);
}
}
This worked on all three example URLs you gave. Then you can pass the validation logic over to service/repository/whatever to validate against your datastore of choice. Then you don't need to do code changes when region/country/courses are added.
And to round out my example, here is the Data clasee:
public class Data
{
public string Region { get; set; }
public string Country { get; set; }
public string Course { get; set; }
}
And View:
#model StackOverflowExamples.Mvc.Controllers.Data
#{ ViewBag.Title = "Details"; }
<h2>Details</h2>
<div>
<h4>Data</h4>
<hr />
<dl class="dl-horizontal">
<dt>#Html.DisplayNameFor(model => model.Region)</dt>
<dd>#Html.DisplayFor(model => model.Region)</dd>
<dt>#Html.DisplayNameFor(model => model.Country)</dt>
<dd>#Html.DisplayFor(model => model.Country)</dd>
<dt>#Html.DisplayNameFor(model => model.Course)</dt>
<dd>#Html.DisplayFor(model => model.Course)</dd>
</dl>
</div>

Execute Controller Method from razor html view in MVC?

Ok so I have an Html.DropDownList and I want to be able to execute a controller method ActionResult output(string test) and send a parameter to it. I have something like this already but I get an Uncaught TypeError: Cannot set property 'action' of null message:
#Html.DropDownList(
"revisions", ViewData["revisions"] as SelectList,
new
{
onchange = "this.form.action = '/Shops/output('test')'; this.form.submit();"
})
How do I go about fixing my code?
If your Action method's parameter name is id,
public ActionResult output(string id)
{
//do something
}
then you may use your form action url like this.(The default routing will take care of rest)
/Shops/output/somestringhere.
If you have a different name, use that as the query string
public ActionResult output(string name)
{
//do something
}
Now use your form action url like
/Shops/output?name=somestringhere
Another suggestion about your code is to avoid Viewdata for rendering the dropdown. Try to use strongly typed view model and it's properties for transfering data to your view. Also try to move your javascript from your view and make it unobutrusive. So that your view stays as clean markup only.
Assuming you want to show a Revision dropdown in a document create view, Add a property to your viewmodel to have the dropdown items.
public class DocumentCreateViewModel
{
//Other properties also here
public List<SelectListItem> Revisions{ set;get;}
public int SelectedRevision { set;get;}
public DocumentCreateViewModel()
{
Revisions=new List<SelectListItem>();
}
}
and in your GET action, fill the dropdown content to the Revisions property.
public ActionResult Create()
{
var vm=new DocumentCreateViewModel();
vm.Revisions=GetRevisionItemsFromSomeWhere();
return View(vm);
}
And in your strongly typed view,
#model DocumentCreateViewModel
#using(Html.Beginform())
{
#Html.DropDownListFor(x => x.SelectedRevision,
new SelectList(Model.Revisions,"Value","Text"), "Select..")
<input type="submit" />
}
Now to handle the form submit on change event of dropdown, add this script.
$(function(){
$("#SelectedRevision").change(function(){
var _this=$(this);
var selectedRevision=_this.val();
$("form#YourFormIDHere")
.attr("action","./Shops/output/"+selectedRevision).submit();
});
});
Instead of hardcoding the url to shops/output, you may use the razor helper method(#Url.Action) to get the proper path.
#Html.DropDownList(
"revisions", ViewData["revisions"] as SelectList,
new
{
onchange = "submitForm();"
})
and your Javacript goes here
function submitForm()
{
var form = document.forms[0];
form = '/Shops/output?test=test';
form.submit();
}

MVC3 URL alternatives

After reviewing A LOT of questions and Internet data, I've solved a problem of mine with getting URL parameter from MVC3 application correctly.
Thing is that there wasn't a fault in coding, but in routing (I'm not so good with routing...).
Here's the current issue.
http://localhost:51561/Report/Details/1
This is the way my application presents Report details, which is good. But when it does it like this, I can't get value from URL parameter, like this
Request.QueryString["id"]
But, when I manually type in URL http://localhost:51561/Report/Details?id=1 it works...
Thing is i like the first URL type, but I don't know how to get parameter from it...
Help, please...
Update:
My Controller actions:
public ViewResult Details(int id)
{
Report report = db.Reports.Find(id);
ViewBag.TestID = Request.QueryString["id"].ToString();
return View(report);
}
public ActionResult Show(int id)
{
Report report = db.Reports.Find(id);
var imageData = report.Image;
return (File(imageData, "image/jpg"));
}
My View:
<div class="display-label">Picture</div>
<div class="display-field">
<img alt="image" src="<%=Url.Action("Show", "Report", new { id = ViewBag.TestID })%>" width="200px" />
</div>
First of all, you shouldn't use Request.QueryString in your application. Apart from that, in the first URL, you don't have a query string, and thus you can't access it (also read this article on msdn about Request.QueryString).
I also would like to suggest you to go through the basic tutorial of ASP.NET MVC3, to be found here. Many things like your question are thoroughly explained there.
To answer your question now, in your first URL example, the 1 in the URL is a parameter of your action (the Details action). You have to add this parameter to your method (action):
public ActionResult Details(int id)
UPDATE:
You have apparently the right action (method) declaration. Now, you can just use the parameter id. So change the Request.QueryString["id"] just by the variable (parameter) id.
public ViewResult Details(int id)
{
Report report = db.Reports.Find(id);
ViewBag.TestID = id;
return View(report);
}
There is no need to apply ToString() on the id, you shouldn't make it when it isn't necessary (you might need it somewhere else, later or so). Just put it in the ViewBag as the original type.
Your Show() method is good :). You have now the id parameter as you needed. (Try to avoid too many parentheses, it makes it look messy and now so clear.)
public ActionResult Show(int id)
{
Report report = db.Reports.Find(id);
var imageData = report.Image;
return File(imageData, "image/jpg");
}
You're not supposed to use Request.QueryString["id"] in MVC
Just add id parameter to your ReportController.Details action:
public ActionResult Details (int id)
The above is assuming you have a default route setup in Global.asax:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

ASP.NET MVC Create on a Master-Detail Relationship

I am displaying an list of Items for a given Order. When a user clicks Add Item I redirect to the Item / Create page. This page collects that necessary input but also needs to know the order id that the item belongs to. What is the appropriate way to pass the OrderID to the Item / Create so that it survives the form post when the newly created item is saved.
I've played with TempData and writing the id out on the detail page via Html.Encode(). It gets me part of the way there in that the id shows up on the item form but the value is lost when the form submits and posts. I suppose because its not part of the formcollection. I am guessing my workaround is not the best way and would like to know if anyone can point out the proper way to do this in asp.net mvc.
I do this by creating a new route for the Item controller that includes the OrderId. It doesn't make sense to have an Item without an Order, so the OrderId is required using the constraints parameter.
routes.MapRoute(
"OrderItems",
"Item/{action}/{orderId}/{id}",
new { controller = "Item" },
new { orderId = #"d+" }
);
So the url would look like http://<sitename>/Item/Create/8, where 8 is the OrderId for which to create an item. Something similar could be done for Delete action routes with http://<sitename>/Item/Delete/8/5, where 8 is the OrderId and 5 is the ItemId.
Your Action methods would look like this:
public ActionResult Create(int orderId)
public ActionResult Delete(int orderId, int id)
You could also set it up so that the urls looked like http://<sitename>/Order/8/Item/Create and http://<sitename>/Order/8/Item/Delete/5 if that seems to more clearly show what's going on.
Then the route would look like this:
routes.MapRoute(
"OrderItems",
"Order/{orderId}/Item/{action}/{id}",
new { controller = "Item" },
new { orderId = #"d+" }
);
I've used this sequence (sorry if there are mistakes, I took this from a working example and modified it for your question):
1) In the Order.Details view (assume Model is of type Order):
...
<%= Html.ActionLink("Create New Item", "Create", "OrderItem", new { orderId = Model.ID }, null)%>
...
2) In the OrderItem.Create action:
public ActionResult Create(int orderId)
{
ViewData["orderId"] = orderId;
return View();
}
3) In the OrderItem.Create view:
...
<% using (Html.BeginForm(new { orderId = ViewData["orderId"] }))
...
4) In the OrderItem.Create POST action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(int orderId)
{
// omitted code to create item, associated with orderId
return RedirectToAction("Details", "Order", new { orderId = orderId });
}
If anyone can think of how to improve on this, or of a better way altogether, please chime in, I'm sort of new to this myself so I'd like to improve.
To round-trip a field that's not part of the normal data entry, I generally use a hidden field in the view, like this:
<%= Html.Hidden("OrderID", Model.OrderID) %>
It looks like a form field, acts like a form field, but the user cannot see it. Make sure you push the correct OrderID into your model from the controller.

Resources