ASP.NET MVC: Route to URL - asp.net-mvc

What's the easiest way to get the URL (relative or absolute) to a Route in MVC? I saw this code here on SO but it seems a little verbose and doesn't enumerate the RouteTable.
Example:
List<string> urlList = new List<string>();
urlList.Add(GetUrl(new { controller = "Help", action = "Edit" }));
urlList.Add(GetUrl(new { controller = "Help", action = "Create" }));
urlList.Add(GetUrl(new { controller = "About", action = "Company" }));
urlList.Add(GetUrl(new { controller = "About", action = "Management" }));
With:
protected string GetUrl(object routeValues)
{
RouteValueDictionary values = new RouteValueDictionary(routeValues);
RequestContext context = new RequestContext(HttpContext, RouteData);
string url = RouteTable.Routes.GetVirtualPath(context, values).VirtualPath;
return new Uri(Request.Url, url).AbsoluteUri;
}
What's a better way to examine the RouteTable and get a URL for a given controller and action?

Use the UrlHelper class: http://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.aspx
You should be able to use it via the Url object in your controller. To map to an action, use the Action method: Url.Action("actionName","controllerName");.
A full list of overloads for the Action method is here: http://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.action.aspx
so your code would look like this:
List<string> urlList = new List<string>();
urlList.Add(Url.Action("Edit", "Help"));
urlList.Add(Url.Action("Create", "Help"));
urlList.Add(Url.Action("Company", "About"));
urlList.Add(Url.Action("Management", "About"));
EDIT: It seems, from your new answer, that your trying to build a sitemap.
Have a look at this Codeplex project: http://mvcsitemap.codeplex.com/. I haven't used it myself, but it looks pretty solid.

How about this (in the controller):
public IEnumerable<SiteMapEntry> SiteMapEntries
{
get
{
var entries = new List<SiteMapEntry>();
foreach (var route in this.Routes)
{
entries.Add(new SiteMapEntry
(
this.Url.RouteUrl(route.Defaults),
SiteMapEntry.ChangeFrequency.Weekly,
DateTime.Now,
1F));
}
return entries;
}
}
Where the controller has member:
public IEnumerable<Route> Routes
Take note of:
this.Url.RouteUrl(route.Defaults)

Related

How to route PUT and DELETE requests for the same url to different controller methods

I was searching for an answer to this question, and found this question, which is indeed very similar. However the solutions(s) posted there don't seem to be working for me... I wonder if it has to do with the question's age.
Given the following URL:
/my/items/6
I want HTTP PUT requests for this URL to be handled by one action method, and HTTP DELETE requests to be handled by another action method. Below are the routes I defined (note these are based in an area, so context is an AreaRegistrationContext instance, if that matters):
context.MapRoute(null,
"my/items/{id}",
new { area = "AreaName", controller = "ControllerName", action = "Replace" },
new
{
httpMethod = new HttpMethodConstraint("POST", "PUT"),
}
);
context.MapRoute(null,
"my/items/{id}",
new { area = "AreaName", controller = "ControllerName", action = "Destroy" },
new
{
httpMethod = new HttpMethodConstraint("POST", "DELETE"),
}
);
URL generation works fine with both of these routes, however there are problems when routing incoming requests. Only the first-declared route correctly maps to its respective action.
I dug into the HttpMethodConstraint source code and discovered that it does not care about the "X-HTTP-Method-Override" parameter, only HttpContext.Request.HttpMethod.
I was able to solve this problem with the following custom route constraint class:
public class HttpMethodOverrideConstraint : HttpMethodConstraint
{
public HttpMethodOverrideConstraint(params string[] allowedMethods)
: base(allowedMethods) { }
protected override bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
var methodOverride = httpContext.Request
.Unvalidated().Form["X-HTTP-Method-Override"];
if (methodOverride == null)
return base.Match(httpContext, route, parameterName,
values, routeDirection);
return
AllowedMethods.Any(m =>
string.Equals(m, httpContext.Request.HttpMethod,
StringComparison.OrdinalIgnoreCase))
&&
AllowedMethods.Any(m =>
string.Equals(m, methodOverride,
StringComparison.OrdinalIgnoreCase))
;
}
}
...and these route definitions:
context.MapRoute(null,
"my/items/{id}",
new { area = "AreaName", controller = "ControllerName", action = "Replace" },
new
{
httpMethod = new HttpMethodOverrideConstraint("POST", "PUT"),
}
);
context.MapRoute(null,
"my/items/{id}",
new { area = "AreaName", controller = "ControllerName", action = "Destroy" },
new
{
httpMethod = new HttpMethodOverrideConstraint("POST", "DELETE"),
}
);
My question: is it really necessary to have a custom route constraint to accomplish this? Or is there any way to make it work out-of-the-box with standard MVC & routing classes?
Action filters are your friend...
HttpDeleteAttribute, HttpPutAttribute, HttpPostAttribute, HttpGetAttribute

ASP.NET MVC Areas in Individual Projects - Refactor AreaRegistration Stuff

I'm trying to molularize my ASP.NET MVC application by moving each Area into their own project. Everything was working fine until i decided to refactor out the AreaRegistration stuff and use my own approach (This way i can also register filters and dependencies within my module). Using reflector i have managed to come up with the following.
First i implement the following interface for each module/area:
public interface IModule {
string ModuleName { get; }
void Initialize(RouteCollection routes);
}
E.g.:
public class BlogsModule : IModule {
public string ModuleName { get { return "Blogs"; } }
public void Initialize(RouteCollection routes) {
routes.MapRoute(
"Blogs_Default",
"Blogs/{controller}/{action}/{id}",
new { area = ModuleName, controller = "Home", action = "Index",
id = UrlParameter.Optional },
new string[] { "Modules.Blogs.Controllers" }
);
}
}
Then in my Global.asax file (Application_Start event) i say:
// Loop over the modules
foreach (var file in Directory.GetFiles(Server.MapPath("~/bin"), "Modules.*.dll")) {
foreach (var type in Assembly.LoadFrom(file).GetExportedTypes()) {
if (typeof(IModule).IsAssignableFrom(type)) {
var module = (IModule)Activator.CreateInstance(type);
module.Initialize(RouteTable.Routes);
}
}
}
I then removed the existing AreaRegistration stuff. Everything is working fine up to this point. When i run my application and render the link to a module, e.g.:
#Html.ActionLink("Blogs", "Index", "Home", new { area = "Blogs" }, null)
The correct url is displayed but when i click on the url it displays the wrong view. After debugging it looks like the url is routed to the correct Action within the HomeController of my Blogs module. However it tries to display the Home/Index.cshtml view in the main project and not the one in the module/area. I'm guessing somewhere along the lines i have missed how to tell the view engine to treat the routed url as an area as it seems to be ignoring the AreaViewLocationFormats (inside the RazorViewEngine).
I'd appreciate it if someone could show me what i'm missing. Thanks
After further refactoring it appears that, the view engine looks for an area data token. I therefore changed the code to add routes in Initialize method of the module as:
// Create the route
var route = new Route("Blogs/{controller}/{action}/{id}", new RouteValueDictionary(new { area = ModuleName, controller = "Home", action = "Index", id = UrlParameter.Optional }), new MvcRouteHandler());
// Add the data tokens
route.DataTokens = new RouteValueDictionary();
route.DataTokens["area"] = this.ModuleName;
route.DataTokens["UseNamespaceFallback"] = false;
route.DataTokens["Namespaces"] = new string[] { "Modules.Blogs.Controllers" };
// Add the route
routes.Add(route);
Hope this helps.

ASPMvc Routing Issues with legacy url

I have got a legacy url that I cannot change, which is output on a page which needs to now post to a new MVC version of the page:
http://somesite.com/somepage?some-guid=xxxx-xxxx
Now I am trying to map this to a new controller but I need to get the some-guid into my controller:
public class MyController : Controller
{
[HttpGet]
public ActionResult DisplaySomething(Guid myGuid)
{
var someResult = DoSomethingWithAGuid(myGuid);
...
}
}
I can change the controller and routes as much as I like, however the legacy url cannot change. So I am a bit stumped as to how I can get access to the some-guid.
I have tried routing with the ?some-guid={myGuid} but the routing doesn't like the ?, so then I tried to let it autobind, but as it contains hyphens it doesn't seem to bind. I was wondering if there was any type of attribute I could use to hint that it should bind from a part of the querystring...
Any help would be great...
I would have thought you would have done a route a bit like this..
routes.MapRoute(
"RouteName", // Name the route
"somepage/{some-guid}", // the Url
new { controller = "MyController", action = "DisplaySomething", some-guid = UrlParameter.Optional }
);
The {some-guid} part of URL matches your url parmater and passes it to the controller.
So if you have your action like so :
public ActionResult DisplaySomething(Guid some-guid)
{
var someResult = DoSomethingWithAGuid(some-guid);
...
}
Give that a go and see how you get on..
routes.MapRoute(
"Somepage", // Route name
"simepage", // URL with parameters
new { controller = "MyController", action = "DisplaySomething"
);
And then in your controller:
public class MyController : Controller {
public ActionResult DisplaySomething(Guid myGuid)
{
var someResult = DoSomethingWithAGuid(myGuid);
...
}
}
Try this:
routes.MapRoute("SomePageRoute","Somepage",
new { controller = "MyController", action = "DisplaySomething" });
And then in your controller:
public ActionResult DisplaySomething() {
Guid sGuid = new Guid(Request.QueryString["some-guid"].ToString());
}

Ambient values in mvc2.net routing

I have following two routes registered in my global.asax file
routes.MapRoute(
"strict",
"{controller}.mvc/{docid}/{action}/{id}",
new { action = "Index", id = "", docid = "" },
new { docid = #"\d+"}
);
routes.MapRoute(
"default",
"{controller}.mvc/{action}/{id}",
new { action = "Index", id = "" },
new { docConstraint = new DocumentConstraint() }
);
and I have a static "dashboard" link in my tabstrip and some other links that are constructed from values in db here is the code
<ul id="globalnav" class = "t-reset t-tabstrip-items">
<li class="bar" id = "dashboard">
<%=Html.ActionLink("dash.board", "Index", pck.Controller, new{docid =string.Empty,id = pck.PkgID }, new { #class = "here" })%>
</li>
<%
foreach (var md in pck.sysModules)
{
%>
<li class="<%=liClass%>">
<%=Html.ActionLink(md.ModuleName, md.ActionName, pck.Controller, new { docid = md.DocumentID}, new { #class = cls })%>
</li>
<%
}
%>
</ul>
Now my launching address is localhost/oa.mvc/index/11 clearly matching the 2nd route. But when I visit any page that has mapped to first route and then come back to dash.board link it shows me localhost/oa.mvc/7/index/11 where 7 is docid and picked from previous Url.
I understand that my action method is after docid and changing it would not clear the docid.
My question here is, can I remove docid in this scenario without changing the route?
I have the same "not clearing out" value problem...
I've stepped into source code and I don't understand the reason for being of segment commented as : // Add all current values that aren't in the URL at all
# System\Web\Routing\ParsedRoute.cs, public BoundUrl Bind(RouteValueDictionary currentValues, RouteValueDictionary values, RouteValueDictionary defaultValues, RouteValueDictionary constraints) method from line 91 to line 100
While the clearing process is correctly handled in method preceding steps, this code "reinjects" the undesired parameter into acceptedValues dictionary!?
My routing is defined this way:
routes.MapRoute(
"Planning",
"Plans/{plan}/{controller}/{action}/{identifier}",
new { controller = "General", action = "Planning", identifier = UrlParameter.Optional },
new { plan = #"^\d+$" }
);
// default application route
routes.MapRoute(
"Default",
"{controller}/{action}/{identifier}",
new {
controller = "General",
action = "Summary",
identifier = UrlParameter.Optional,
plan = string.Empty // mind this default !!!
}
);
This is very similar to what you're using. But mind my default route where I define defaults. Even though my default route doesn't define plan route value I still set it to string.Empty. So whenever I use Html.ActionLink() or Url.Action() and I want plan to be removed from the URL I call it the usual way:
Url.Action("Action", "Controller", new { plan = string.Empty });
And plan is not included in the URL query string any more. Try it out yourself it may work as well.
Muhammad, I suggest something like this :
(written 5 mn ago, not tested in production)
public static class MyHtmlHelperExtensions {
public static MvcHtmlString FixActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes) {
var linkRvd = new RouteValueDictionary(routeValues);
var contextRvd = htmlHelper.ViewContext.RouteData.Values;
var contextRemovedRvd = new RouteValueDictionary();
// remove clearing route values from current context
foreach (var rv in linkRvd) {
if (string.IsNullOrEmpty((string)rv.Value) && contextRvd.ContainsKey(rv.Key)) {
contextRemovedRvd.Add(rv.Key, contextRvd[rv.Key]);
contextRvd.Remove(rv.Key);
}
}
// call ActionLink with modified context
var htmlString = htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
// restore context
foreach (var rv in contextRemovedRvd) {
contextRvd.Add(rv.Key, rv.Value);
}
return htmlString;
}
}
This is such a frustrating problem and I would venture to say that it is even a bug in ASP.Net MVC. Luckily it's an easy fix using ActionFilters. If you are using MVC3 then I would just put this as a global attribute to clear out ambient values. I made this attribute discriminatory, but you can change it to clear all attributes.
The assumption here is that by the time the Result is executing (your view most likely), you have already explicitly specified all your ActionLinks and Form Actions. Thus this will execute before they (the links) are evaluated, giving you a new foundation to generate them.
public class ClearAmbientRouteValuesAttribute : ActionFilterAttribute
{
private readonly string[] _keys;
public ClearAmbientRouteValuesAttribute(params string [] keys)
{
if (keys == null)
_keys = new string[0];
_keys = keys;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
foreach (var key in _keys) {
// Why are you sticking around!!!
filterContext.RequestContext.RouteData.Values.Remove(key);
}
}
}
// Inside your Global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new ClearAmbientRouteValuesAttribute("format"));
}
Hope this helps someone, cause it sure helped me. Thanks for asking this question.
In this particular scenario I have two recommendations:
Use named routes. The first parameter to the MapRoute method is a name. To generate links use Html.RouteLink() (and other similar APIs). This way you'll always choose the exact route that you want and never have to wonder what gets chosen.
If you still want to use Html.ActionLink() then explicitly set docid="" to clear out its value.
Here's how I solved my problem, it may take a little adapting to get it to work, but I felt like I could get what I needed and just use routing more or less normally:
Excerpted from Apress Pro ASP.Net.MVC 3 Framework:
A value must be available for every segment variable defined in the URL pattern.
To find values for each segment variable, the routing system looks first at the
values we have provided (using the properties of anonymous type), then the
variable values for the current request, and finally at the default values defined in
the route. (We return to the second source of these values later in this chapter.)
None of the values we provided for the segment variables may disagree with the
default-only variables defined in the route. These are variables for which default
values have been provided, but which do not occur in the URL pattern. For
example, in this route definition, myVar is a default-only variable:
routes.MapRoute("MyRoute", "{controller}/{action}",
new { myVar = "true" });
For this route to be a match, we must take care to not supply a value for myVar or to make
sure that the value we do supply matches the default value.
The values for all of the segment variables must satisfy the route constraints. See
the “Constraining Routes” section earlier in the chapter for examples of different
kinds of constraints.
Basically I used the rule about a route not matching if it doesn't define a segment, but has a default variable used to give me a little more control over whether a route was chosen for outbound routing or not.
Here's my fixed routes, notice how I specify a value for category that would never be valid and don't specify a segment for category. This means that route will be skipped if I have a category, but will use it if I only have a page:
routes.MapRoute(null, "receptionists/faq/{page}", new { controller = "Receptionist", action = "Faq", page = 1, category = (Object)null }, new { page = #"^\d+$" });
routes.MapRoute(null, "receptionists/faq/{category}/{page}", new { controller = "Receptionist", action = "Faq", page = 1 }, new { category = #"^\D+$", page = #"^\d+$" });
For Category Links
#Html.ActionLink("All", "Faq", new { page = 1 })
#foreach (var category in Model.Categories)
{
#Html.ActionLink(category.DisplayName, "faq", new { category = category.DisplayName.ToLower(), page = 1 })
}
For Page Links
#for (var p = 1; p <= Model.TotalPages; p++)
{
#Html.ActionLink(p.ToString(), "Faq", new { page = p, category = Model.CurrentCategory})
}

Asp.net MVC ModelState.Clear

Can anyone give me a succinct definition of the role of ModelState in Asp.net MVC (or a link to one). In particular I need to know in what situations it is necessary or desirable to call ModelState.Clear().
Bit open ended huh... sorry, I think it might help if tell you what I'm acutally doing:
I have an Action of Edit on a Controller called "Page". When I first see the form to change the Page's details everything loads up fine (binding to a "MyCmsPage" object). Then I click a button that generates a value for one of the MyCmsPage object's fields (MyCmsPage.SeoTitle). It generates fine and updates the object and I then return the action result with the newly modified page object and expect the relevant textbox (rendered using <%= Html.TextBox("seoTitle", page.SeoTitle)%>) to be updated ... but alas it displays the value from the old model that was loaded.
I've worked around it by using ModelState.Clear() but I need to know why / how it has worked so I'm not just doing it blindly.
PageController:
[AcceptVerbs("POST")]
public ActionResult Edit(MyCmsPage page, string submitButton)
{
// add the seoTitle to the current page object
page.GenerateSeoTitle();
// why must I do this?
ModelState.Clear();
// return the modified page object
return View(page);
}
Aspx:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyCmsPage>" %>
....
<div class="c">
<label for="seoTitle">
Seo Title</label>
<%= Html.TextBox("seoTitle", page.SeoTitle)%>
<input type="submit" value="Generate Seo Title" name="submitButton" />
</div>
I think is a bug in MVC. I struggled with this issue for hours today.
Given this:
public ViewResult SomeAction(SomeModel model)
{
model.SomeString = "some value";
return View(model);
}
The view renders with the original model, ignoring the changes. So I thought, maybe it does not like me using the same model, so I tried like this:
public ViewResult SomeAction(SomeModel model)
{
var newModel = new SomeModel { SomeString = "some value" };
return View(newModel);
}
And still the view renders with the original model. What's odd is, when I put a breakpoint in the view and examine the model, it has the changed value. But the response stream has the old values.
Eventually I discovered the same work around that you did:
public ViewResult SomeAction(SomeModel model)
{
var newModel = new SomeModel { SomeString = "some value" };
ModelState.Clear();
return View(newModel);
}
Works as expected.
I don't think this is a "feature," is it?
Update:
This is not a bug.
Please stop returning View() from a POST action. Use PRG instead and redirect to a GET if the action is a success.
If you are returning a View() from a POST action, do it for form validation, and do it the way MVC is designed using the built in helpers. If you do it this way then you shouldn't need to use .Clear()
If you're using this action to return ajax for a SPA, use a web api controller and forget about ModelState since you shouldn't be using it anyway.
Old answer:
ModelState in MVC is used primarily to describe the state of a model object largely with relation to whether that object is valid or not. This tutorial should explain a lot.
Generally you should not need to clear the ModelState as it is maintained by the MVC engine for you. Clearing it manually might cause undesired results when trying to adhere to MVC validation best practises.
It seems that you are trying to set a default value for the title. This should be done when the model object is instantiated (domain layer somewhere or in the object itself - parameterless ctor), on the get action such that it goes down to the page the 1st time or completely on the client (via ajax or something) so that it appears as if the user entered it and it comes back with the posted forms collection. Some how your approach of adding this value on the receiving of a forms collection (in the POST action // Edit) is causing this bizarre behaviour that might result in a .Clear() appearing to work for you. Trust me - you don't want to be using the clear. Try one of the other ideas.
If you want to clear a value for an individual field then I found the following technique useful.
ModelState.SetModelValue("Key", new ValueProviderResult(null, string.Empty, CultureInfo.InvariantCulture));
Note:
Change "Key" to the name of the field that you want to reset.
Well lots of us seem to have been bitten by this, and although the reason this happens makes sense I needed a way to ensure that the value on my Model was shown, and not ModelState.
Some have suggested ModelState.Remove(string key), but it's not obvious what key should be, especially for nested models. Here are a couple methods I came up with to assist with this.
The RemoveStateFor method will take a ModelStateDictionary, a Model, and an expression for the desired property, and remove it. HiddenForModel can be used in your View to create a hidden input field using only the value from the Model, by first removing its ModelState entry. (This could easily be expanded for the other helper extension methods).
/// <summary>
/// Returns a hidden input field for the specified property. The corresponding value will first be removed from
/// the ModelState to ensure that the current Model value is shown.
/// </summary>
public static MvcHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression)
{
RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression);
return helper.HiddenFor(expression);
}
/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this ModelStateDictionary modelState, TModel model,
Expression<Func<TModel, TProperty>> expression)
{
var key = ExpressionHelper.GetExpressionText(expression);
modelState.Remove(key);
}
Call from a controller like this:
ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);
or from a view like this:
#Html.HiddenForModel(m => m.MySubProperty.MySubValue)
It uses System.Web.Mvc.ExpressionHelper to get the name of the ModelState property.
Well the ModelState basically holds the current State of the model in terms of validation, it holds
ModelErrorCollection: Represent the errors when the model try to bind the values.
ex.
TryUpdateModel();
UpdateModel();
or like a parameter in the ActionResult
public ActionResult Create(Person person)
ValueProviderResult: Hold the details about the attempted bind to the model.
ex. AttemptedValue, Culture, RawValue.
Clear() method must be use with caution because it can lead to unspected results. And you will lose some nice properties of the ModelState like AttemptedValue, this is used by MVC in the background to repopulate the form values in case of error.
ModelState["a"].Value.AttemptedValue
I had an instance where I wanted to update the model of a sumitted form, and did not want to 'Redirect To Action' for performanace reason. Previous values of hidden fields were being retained on my updated model - causing allsorts of issues!.
A few lines of code soon identified the elements within ModelState that I wanted to remove (after validation), so the new values were used in the form:-
while (ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")).Value != null)
{
ModelState.Remove(ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")));
}
I wanted to update or reset a value if it didn't quite validate, and ran into this problem.
The easy answer, ModelState.Remove, is.. problematic.. because if you are using helpers you don't really know the name (unless you stick by the naming convention). Unless perhaps you create a function that both your custom helper and your controller can use to get a name.
This feature should have been implemented as an option on the helper, where by default is does not do this, but if you wanted the unaccepted input to redisplay you could just say so.
But at least I understand the issue now ;).
Got it in the end. My Custom ModelBinder which was not being registered and does this :
var mymsPage = new MyCmsPage();
NameValueCollection frm = controllerContext.HttpContext.Request.Form;
myCmsPage.SeoTitle = (!String.IsNullOrEmpty(frm["seoTitle"])) ? frm["seoTitle"] : null;
So something that the default model binding was doing must have been causing the problem. Not sure what, but my problem is at least fixed now that my custom model binder is being registered.
Generally, when you find yourself fighting against a framework standard practices, it is time to reconsider your approach. In this case, the behavior of ModelState. For instance, when you don't want model state after a POST, consider a redirect to the get.
[HttpPost]
public ActionResult Edit(MyCmsPage page, string submitButton)
{
if (ModelState.IsValid) {
SomeRepository.SaveChanges(page);
return RedirectToAction("GenerateSeoTitle",new { page.Id });
}
return View(page);
}
public ActionResult GenerateSeoTitle(int id) {
var page = SomeRepository.Find(id);
page.GenerateSeoTitle();
return View("Edit",page);
}
EDITED to answer culture comment:
Here is what I use to handle a multi-cultural MVC application. First the route handler subclasses:
public class SingleCultureMvcRouteHandler : MvcRouteHandler {
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var culture = requestContext.RouteData.Values["culture"].ToString();
if (string.IsNullOrWhiteSpace(culture))
{
culture = "en";
}
var ci = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
return base.GetHttpHandler(requestContext);
}
}
public class MultiCultureMvcRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var culture = requestContext.RouteData.Values["culture"].ToString();
if (string.IsNullOrWhiteSpace(culture))
{
culture = "en";
}
var ci = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
return base.GetHttpHandler(requestContext);
}
}
public class CultureConstraint : IRouteConstraint
{
private string[] _values;
public CultureConstraint(params string[] values)
{
this._values = values;
}
public bool Match(HttpContextBase httpContext,Route route,string parameterName,
RouteValueDictionary values, RouteDirection routeDirection)
{
// Get the value called "parameterName" from the
// RouteValueDictionary called "value"
string value = values[parameterName].ToString();
// Return true is the list of allowed values contains
// this value.
return _values.Contains(value);
}
}
public enum Culture
{
es = 2,
en = 1
}
And here is how I wire up the routes. After creating the routes, I prepend my subagent (example.com/subagent1, example.com/subagent2, etc) then the culture code. If all you need is the culture, simply remove the subagent from the route handlers and routes.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("Content/{*pathInfo}");
routes.IgnoreRoute("Cache/{*pathInfo}");
routes.IgnoreRoute("Scripts/{pathInfo}.js");
routes.IgnoreRoute("favicon.ico");
routes.IgnoreRoute("apple-touch-icon.png");
routes.IgnoreRoute("apple-touch-icon-precomposed.png");
/* Dynamically generated robots.txt */
routes.MapRoute(
"Robots.txt", "robots.txt",
new { controller = "Robots", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Sitemap", // Route name
"{subagent}/sitemap.xml", // URL with parameters
new { subagent = "aq", controller = "Default", action = "Sitemap"}, new[] { "aq3.Controllers" } // Parameter defaults
);
routes.MapRoute(
"Rss Feed", // Route name
"{subagent}/rss", // URL with parameters
new { subagent = "aq", controller = "Default", action = "RSS"}, new[] { "aq3.Controllers" } // Parameter defaults
);
/* remap wordpress tags to mvc blog posts */
routes.MapRoute(
"Tag", "tag/{title}",
new { subagent = "aq", controller = "Default", action = "ThreeOhOne", id = UrlParameter.Optional}, new[] { "aq3.Controllers" }
).RouteHandler = new MultiCultureMvcRouteHandler(); ;
routes.MapRoute(
"Custom Errors", "Error/{*errorType}",
new { controller = "Error", action = "Index", id = UrlParameter.Optional}, new[] { "aq3.Controllers" }
);
/* dynamic images not loaded from content folder */
routes.MapRoute(
"Stock Images",
"{subagent}/Images/{*filename}",
new { subagent = "aq", controller = "Image", action = "Show", id = UrlParameter.Optional, culture = "en"}, new[] { "aq3.Controllers" }
);
/* localized routes follow */
routes.MapRoute(
"Localized Images",
"Images/{*filename}",
new { subagent = "aq", controller = "Image", action = "Show", id = UrlParameter.Optional}, new[] { "aq3.Controllers" }
).RouteHandler = new MultiCultureMvcRouteHandler();
routes.MapRoute(
"Blog Posts",
"Blog/{*postname}",
new { subagent = "aq", controller = "Blog", action = "Index", id = UrlParameter.Optional}, new[] { "aq3.Controllers" }
).RouteHandler = new MultiCultureMvcRouteHandler();
routes.MapRoute(
"Office Posts",
"Office/{*address}",
new { subagent = "aq", controller = "Offices", action = "Address", id = UrlParameter.Optional }, new[] { "aq3.Controllers" }
).RouteHandler = new MultiCultureMvcRouteHandler();
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { subagent = "aq", controller = "Home", action = "Index", id = UrlParameter.Optional }, new[] { "aq3.Controllers" } // Parameter defaults
).RouteHandler = new MultiCultureMvcRouteHandler();
foreach (System.Web.Routing.Route r in routes)
{
if (r.RouteHandler is MultiCultureMvcRouteHandler)
{
r.Url = "{subagent}/{culture}/" + r.Url;
//Adding default culture
if (r.Defaults == null)
{
r.Defaults = new RouteValueDictionary();
}
r.Defaults.Add("culture", Culture.en.ToString());
//Adding constraint for culture param
if (r.Constraints == null)
{
r.Constraints = new RouteValueDictionary();
}
r.Constraints.Add("culture", new CultureConstraint(Culture.en.ToString(), Culture.es.ToString()));
}
}
}
Well, this seemed to work on my Razor Page and never even did a round trip to the .cs file.
This is old html way. It might be useful.
<input type="reset" value="Reset">

Resources