.NET MVC routing using attributes only - asp.net-mvc

Does anyone know how to create a route in .NET MVC which contains attributes only? My code is as follows:
[Route("{listingCategoryDescription}/")]
public ActionResult CategorySearch(string listingCategoryDescription)
{
As you can probably tell I want the URL to simply contain a category. Is this possible? Or do I need to hard code at least one part of the route?
Many thanks.

Are you wanting the category passed in at the root URL? Like http://localhost/apples?
If so, this will work, but you will likely collide with other routes.
public class CategoriesController : Controller
{
[Route("{listingCategoryDescription}")]
public ActionResult Index(string listingCategoryDescription)
{
return Content("You picked category: " + listingCategoryDescription);
}
}

Related

How to post a custom model to a controller in Umbraco 7.5.8?

I have a Document type, Template, and a page in the CMS content tree which uses these for a Contact page. The document type has no CMS data properties, because it doesn't need any. I use Models Builder for other pages with no issue, but for this page I've created my own custom model within my MVC project.
I've read every tutorial I can find, and looked at every forum post and issue on the Umbraco forums and Stackoverflow, and for the life of me I can't figure out what I'm doing wrong. The model name and namespace do not conflict with the autogenerated Models builder one.
My understanding is for posting forms a SurfaceController is the way to go - a RenderController is intended more for presenting stuff. So my controller extends SurfaceController. uses Umbraco.BeginUmbracoForm(etc)
I've tried every combination of SurfaceController and RenderController with UmbracoTemplatePage, UmbracoViewPage and every way of changing my model to extend both RenderModel and IPublishedContent to test each. When trying RenderController I've overridden default Index method with RenderModel parameter to create an instance of my model with the renderModel parameter.
Usually the error I get is "Cannot bind source type Umbraco.Web.Models.RenderModel to model type xxx". Sometimes combinations I've attempted allow the Get to succeed, then give this error on Post.
I've even tried to remove the page from the CMS and use a standard MVC controller and route - this allows me to display the page, and even using a standard Html.BeginForm on my view, I get an error when trying to post the form (despite a breakpoint in the code in controller being hit) which also states it "Cannot bind source type Umbraco.Web.Models.RenderModel to model type xxx"
This CANNOT be this difficult. I'm ready to throw laptop out window at this stage.
What am I doing wrong???? Or without seeing my code at least can anyone tell me how this is supposed to be done? How do you post a custom model form to an Umbraco 7.5 controller, with no CMS published content properties required?
As it stands, my View looks like this:
#inherits UmbracoViewPage<Models.Contact>
...
using (Html.BeginUmbracoForm<ContactController>("Contact", FormMethod.Post
My Controller looks like this:
public class ContactController : SurfaceController
{
public ActionResult Contact()
{
return View("~/Views/Contact.cshtml");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(Contact contactForm)
{
...
}
And my model looks like this:
public class Contact : RenderModel
{
public Contact() : base(UmbracoContext.Current.PublishedContentRequest.PublishedContent, UmbracoContext.Current.PublishedContentRequest.Culture)
{
}
public Contact(IPublishedContent content) : base(content, CultureInfo.CurrentUICulture)
{
}
[Display(Name = "First Name", Prompt = "First Name")]
public string FirstName { get; set; }
...
Update: If I use the model for my CMS page created automatically by models builder, the Get and Post work ok. However when I customise the model (i.e. I put a partial class of the same name in ~/App_Data/Models and regenerate models on Developer tab), the custom properties in my posted model are always null.
I can populate these manually from the request form variables, however this seems wrong and messy. What's going on here?
public class ContactPageController : SurfaceController
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactPage contactForm)
{
try
{
contactForm.FirstName = Request.Form["FirstName"];
contactForm.LastName = Request.Form["LastName"];
contactForm.EmailAddress = Request.Form["EmailAddress"];
contactForm.Telephone = Request.Form["Telephone"];
contactForm.Message = Request.Form["Message"];
var captchaIsValid = ReCaptcha.Validate(ConfigurationManager.AppSettings["ReCaptcha:SecretKey"]);
if (ModelState.IsValid && captchaIsValid)
{
// Do what you need
TempData["EmailSent"] = true;
return RedirectToCurrentUmbracoPage();
}
if (!captchaIsValid)
{
ModelState.AddModelError("ReCaptchaError", "Captcha validation failed - Please try again.");
}
return RedirectToCurrentUmbracoPage();
}
catch (Exception ex)
{
LogHelper.Error(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType, null, ex);
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
}
Further Info: Robert's approach first time, thanks for that. I had tried one approach using PartialViews and ChildActions but clearly I didn't do it correctly. Would I be right in saying the reason this approach is required at all (i.e. why you can't add the custom properties to the model the main view is bound to) is because I am using Models Builder?
So among the strange errors I received was one about 2 classes both wanting to represent the contact page, and another about it expecting one type of class in a dictionary (ContactPage) but receiving another (Contact) - even though I had made no reference to ContactPage in either view or controller. This suggests to me ModelsBuilder adds a mapping for document types to models on app startup behind the scenes? Which is maybe why you're better to take this approach of letting ModelsBuilder do its thing, and build your own model on top of that with a partial view in this way?
I've found the quality of documentation on this topic very poor indeed. Not sure if maybe the good stuff is behind closed doors, i.e. requires a paid Umbraco membership? For a supposedly open source system, that feels kinda shady to me.
Easy when you know how!!
For your situation, SurfaceController is the most likely candidate as you've surmised, however think of the actions in that Controller as applying to partial views, not the full view used by the page.
Don't try to use the ModelsBuilder ContactPage model, but rather create your own as you were originally (the original Contact model) - think of it as a Data Transfer Object perhaps if you do need to apply any of the properties back to the ContactPage model for any reason.
I've found I've had the greatest success with SurfaceController with the following conditions:
The Page Template does not inherit from the model intended for the Form; it inherits directly from the standard Umbraco PublishedContentModel or a ModelsBuilder generated model.
Implement a Partial View for the action defined in your SurfaceController - this view inherits from Umbraco.Web.Mvc.UmbracoViewPage<Contact> in your example.
The Partial View utilises BeginUmbracoForm<ContactController>, specifying POST action as one of the parameters (depending on which signature you're using)
You shouldn't need to populate any model properties using Request.Form like this.
For example, the code in one of my projects looks something like this:
SurfaceController
public class FormsController : SurfaceController
{
[ChildActionOnly]
public ActionResult ContactUs()
{
return PartialView(new ContactForm ());
}
[HttpPost]
public async Task<ActionResult> HandleContactUs(ContactForm model)
{
if (ModelState.IsValid)
{
if (!await model.SendMail(Umbraco)) // Do something with the model.
{
}
}
return RedirectToCurrentUmbracoPage(); // Send us back to the page our partial view is on
}
}
Partial View:
#inherits Umbraco.Web.Mvc.UmbracoViewPage<ContactForm>
#using Digitalsmith.ReCaptcha
#using (Html.BeginUmbracoForm<FormsController>("HandleContactUs"))
{
...
}
Contact Page Template:
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage<ContactPage>
#{
Layout = "_Layout.cshtml";
}
#Html.Action("ContactUs", "Forms")

Configure access to View's SubFolders in ASP.NET MVC

I am wondering if it is possible to do following with ASP.NET MVC 5.
I would like to have a OrderController and the following folder's structure
View/Orders/Details/
I need to know how we can configure methods for Details folder?
I mean Create/Edit/List.
Have we use some method attribute for it or routing and how it should be done?
Thanks!
P.S.
I found very useful this link http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx
You have two options. You can either make your own code to determine the correct view to return which is fairly complex or you can specify the view you need using the full path. Additionally if you have to have methods with the same (not sure why you would want that), then you would need to change your routing. An option is to use attribute routing.
public class OrdersController : Controller
{
[Route("CreateOrder")]
public ActionResult Create(Order order)
{
//Snip
return View("~/Views/Orders/Details/Create.cshtml");
}
[Route("CreateOrderDetails")]
public ActionResult Create(OrderDetails orderDetails)
{
//Snip
return View("~/Views/Orders/Details/Create.cshtml");
}
}

Purpose of ActionName

What's the benefit of setting an alias for an action method using the "ActionName" attribute? I really don't see much benefit of it, in providing the user the option to call an action method with some other name. After specifying the alias, the user is able to call the action method only using the alias. But if that is required then why doesn't the user change the name of the action method rather then specifying an alias for it?
I would really appreciate if anyone can provide me an example of the use of "ActionName" in a scenario where it can provide great benefit or it is best to use.
It allows you to start your action with a number or include any character that .net does not allow in an identifier. - The most common reason is it allows you have two Actions with the same signature (see the GET/POST Delete actions of any scaffolded controller)
For example: you could allow dashes within your url action name http://example.com/products/create-product vs http://example.com/products/createproduct or http://example.com/products/create_product.
public class ProductsController {
[ActionName("create-product")]
public ActionResult CreateProduct() {
return View();
}
}
It is also useful if you have two Actions with the same signature that should have the same url.
A simple example:
public ActionResult SomeAction()
{
...
}
[ActionName("SomeAction")]
[HttpPost]
public ActionResult SomeActionPost()
{
...
}
I use it when the user downloads a report so that they can open their csv file directly into Excel easily.
[ActionName("GetCSV.csv")]
public ActionResult GetCSV(){
string csv = CreateCSV();
return new ContentResult() { Content = csv, ContentEncoding = System.Text.Encoding.UTF8, ContentType = "text/csv" };
}
Try this code:
public class ProductsController
{
[ActionName("create-product")]
public ActionResult CreateProduct()
{
return View("CreateProduct");
}
}
It is also helpful when you need to implement method overloading.
public ActionResult ActorView()
{
return View(actorsList);
}
[ActionName("ActorViewOverload")]
public ActionResult ActorView(int id)
{
return RedirectToAction("ActorView","Home");
}
`
Here one ActorView accepts no parameters and the other accepts int.
The first method used for viewing actor list and the other one is used for showing the same actor list after deleting an item with ID as 'id'.
You can use action name as 'ActorViewOverload' whereever you need method overloading.
This class represents an attribute that is used for the name of an action. It also allows developers to use a different action name than the method name.

ASP.NET MVC, Reeling in Unroutable Page Nesting

The concept of routes is nothing new, and it works great for the concept of {area}/{controller}/{action}/{parameter}, but few sites are standalone UI interaction.
Websites often need parts of themselves that aren't really dedicated to taking data, but presenting it. For instance one of the sites I am working on has a large part of itself dedicated to user interaction (which the MVC system solves expertly. A Membership area, a place to manage information, a way to purchase items, etc.) - but it also needs a part that functions more like an old-fashioned website, where you're simply looking at pages like a folder structure.
one solution I have found is to try a custom view engine. This worked, but I quick found myself lost in a convoluted routing scheme. Another I guess I could go with is to just have an IgnoreRoute and put files in the ignored folder like normal html/aspx, but I'd really rather have the option of using Controllers so that there is a chance I can have data returned from a database, etc in the future.
So let me show you my current scenario...
Areas
Membership
Rules
Controllers
HomeController
FileView(string folder, string file)
Views
Home
General
Customize
Content
yyy.cshtml
xxx.cshtml
#Html.Partial("Content/yyy.cshtml")
xxx.cshtml
xxx.cshtml
etc. The Rules area is basically setup to function like a normal /folder/file/ structure. So here is my Controller for it..
public class HomeController : Controller
{
//
// GET: /Information/Home/
public ActionResult Index()
{
return View();
}
// **************************************
// URL: /Rules/{controller}/{folder}/{file}
// **************************************
public ViewResult FileView(string folder, string filename)
{
return View(String.Format("{0}/{1}", folder, filename));
}
}
Now, if I have a category, I simply have a lightweight controller that inherits from that Area's HomeController, like this...
public class GeneralController : Rules.Controllers.HomeController
{
// **************************************
// URL: /Rules/General/Customize/{id}
// **************************************
public ViewResult Customize(string id)
{
return FileView("Customize", id);
}
}
So then, for each folder in the 'sub' controller, I have a single Route that takes in the name of the file.
This works, but I feel it's excessively clunky. Can anyone suggest a better alternative? There are just too many pages, and too much nesting, to have a full ActionResult for each one. I also want to maintain clean urls.
Perhaps you can use a catch-all route for the Membership area, route it to a controller (MembershipController?) and have that controller just render the view that is catched by the route, like this:
public class MembershipController : Controller
{
public ActionResult Index(string pageTitle)
{
return View(pageTitle);
}
}
And the route:
routes.MapRoute(
"Membership",
"Membership/{*pageTitle}",
new {controller = "Membership", action = "Index", pageTitle = "NotFound"});
Of course, in the controller you should check whether the view exists or not, but this one should get you moving. Although I don't see why you want to have MVC in front of this when you just want to display (static?) content.

.NET MVC: Map Route Using Attribute On Action

I'm pretty new to MVC and can't find an answer one way or another to this question. Is there a built in architecture in MVC 1 (or 2, I suppose) that allows you to specify a route mapping via an attribute on a specific action method, rather than in the Global.asax? I can see its use being limited to a degree as multiple methods can be tied to the same action thusly requiring routes to be unnecessarily duplicated, but my question still remains.
Also, does anyone see any gotcha's in implementing something like this, aside from the one I just mentioned about the same action on multiple methods?
Note: I'm not asking HOW to implement this. Only checking if something like this exists, and if not, if it's more trouble than it's worth.
You can also try AttributeRouting, which is available via NuGet. Disclosure -- I am the project author. I've used this in personal and professional projects with great success and would not go back to the default routing mechanism of ASP.NET MVC unless I had to. Take a look at the github wiki. There's extensive documentation of the many features there.
Simple usage looks like this:
public class RestfulTestController : Controller
{
[GET("Resources")]
public ActionResult Index()
{
return Content("");
}
[POST("Resources")]
public ActionResult Create()
{
return Content("");
}
[PUT("Resources/{id}")]
public ActionResult Update(int id)
{
return Content("");
}
[DELETE("Resources/{id}")]
public ActionResult Destroy(int id)
{
return Content("");
}
}
AttributeRouting is highly configurable and has a few extension points. Check it out.
I would recommend ASP.NET MVC Attribute Based Route Mapper for this. This is third party library and does not come with ASP.NET MVC 1 or 2. Usage is like the following:
public SiteController : Controller
{
[Url("")]
public ActionResult Home()
{
return View();
}
[Url("about")]
public ActionResult AboutUs()
{
return View();
}
[Url("store/{category?}")]
public ActionResult Products(string category)
{
return View();
}
}
Then in your global.asax, you just call routes.MapRoutes() to register your action routes.
It's dead simple to implement this.

Resources