stackoverflow URL rewrite - asp.net-mvc

How does SO perform the URL rewrite if we only put in the question ID?
questions/{id}/{whatever}
to
questions/{id}/{question-slug}
I've been working for some time with MVC and I have it working (routes, action, everything) so that it picks up the right content based on the provided ID.
However, the part after the {id} (the slug part) stays the same as typed in. So if someone typed in content/5/foobar it will display the right content but will leave the foobar in there.
In the controller (or somewhere else, please suggest where) I would need to go into the DB and pull out the right slug, put it in the route data and then perform a redirect to the same action with this correct data, I guess?
This is a try with Execute Result override. It works but does not redirect or replace/display the correct URL in browser:
protected override void Execute(System.Web.Routing.RequestContext requestContext) {
if (requestContext.RouteData.Values["id"] != null) {
string currentSlug = _repository.Find(int.Parse(requestContext.RouteData.Values["id"].ToString())).Slug;
if (requestContext.RouteData.Values["slug"] != null) {
requestContext.RouteData.Values.Remove("slug");
}
requestContext.RouteData.Values.Add("slug", currentSlug);
}
base.Execute(requestContext);
}
This is another, nicely working, version of a Display action, so you can see what it does and get an idea what I want:
//
// GET: {culture}/directory/5/{slug}
public virtual ActionResult Display(int id, string slug)
{
var model = _repository.Find(id);
if (model != null) {
if (!model.Slug.Equals(slug, System.StringComparison.OrdinalIgnoreCase)) {
return RedirectToActionPermanent(pndng.DirectoryEntry.ActionNames.Display, pndng.DirectoryEntry.Name, new { id = model.Id, slug = model.Slug });
}
return View(model);
}
// no model found
return InvokeHttp404(HttpContext);
}
This one performs permanent redirect (it does what I want) but is it right?
I guess I need a redirect to refresh the browser URL, don't I?

public ActionResult Details(int id, string slug)
{
var session = MvcApplication.CurrentRavenSession;
var blogPostRelations = session
.Query<BlogPost, BlogPosts_WithRelatedData>()
.Where(x => x.IntId == id)
.As<BlogPostRelations>()
.FirstOrDefault()
;
if (blogPostRelations == null)
return HttpNotFound();
if (blogPostRelations.BlogPost.DisplayData.Slug.Value != slug)
return RedirectToActionPermanent("Details", new { id = id, slug = blogPostRelations.BlogPost.DisplayData.Slug.Value });
return View(blogPostRelations);
}
Notice the:
if (blogPostRelations.BlogPost.DisplayData.Slug.Value != slug)
return RedirectToActionPermanent("Details", new { id = id, slug = blogPostRelations.BlogPost.DisplayData.Slug.Value });
So your #2 approach is the right one.

You could write a custom route for this:
public class QuestionsRoute : Route
{
public QuestionsRoute()
: base(
"questions/{id}/{slug}",
new RouteValueDictionary(new
{
controller = "questions",
action = "index",
slug = UrlParameter.Optional
}),
new RouteValueDictionary(new
{
id = #"\d+"
}),
new MvcRouteHandler()
)
{ }
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
var id = rd.GetRequiredString("id");
var slug = rd.Values["slug"] as string;
if (string.IsNullOrEmpty(slug))
{
slug = GoFetchSlugFromDb(id);
if (string.IsNullOrEmpty(slug))
{
return null;
}
httpContext.Response.RedirectToRoutePermanent(new
{
action = "index",
controller = "questions",
id = id,
slug = slug
});
return null;
}
return rd;
}
private string GoFetchSlugFromDb(string id)
{
// TODO: you know what to do here
throw new NotImplementedException();
}
}
which will be registered in Application_Start:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("questions", new QuestionsRoute());
}
Now your QuestionsController will be pretty simple:
public class QuestionsController: Controller
{
public ActionResult Index(int id, string slug)
{
...
}
}

Related

ASP.NET MVC - Check for duplicate in country name without using Model Annotation

I have a ViewModel and Repository that are being used by the Controller Action for Create
Repository
BackendEntities entity = new BackendEntities();
public void AddCountry(CountriesViewModel countryModel)
{
COUNTRIES2 newCountry = new COUNTRIES2()
{
COUNTRY_ID = countryModel.COUNTRY_ID,
COUNTRY_CODE = countryModel.COUNTRY_CODE,
COUNTRY_NAME = countryModel.COUNTRY_NAME,
ACTION_STATUS = countryModel.ACTION_STATUS,
CREATED_BY = countryModel.CREATED_BY,
CREATED_DATE = countryModel.CREATED_DATE
};
entity.COUNTRIES.Add(newCountry);
entity.SaveChanges();
}
Then, I call the Repository from the Controller Action for Create.
Controller
public ActionResult Create(FormCollection collection, CountriesViewModel countries)
{
CountriesRepository countryRepo = new CountriesRepository();
if (ModelState.IsValid)
{
try
{
// TODO: Add update logic here
countryRepo.AddCountry(countries);
//countryRepo.
var notif = new UINotificationViewModel()
{
notif_message = "Record saved successfully",
notif_type = NotificationType.SUCCESS,
};
TempData["notif"] = notif;
return RedirectToAction("Index");
}
catch (Exception e)
{
this.AddNotification("Country cannot be added.<br/> Kindly verify the data.", NotificationType.ERROR);
}
}
return View(countries);
}
Please how do I Validate for duplicate COUNTRY_NAME using the condition, where ACTION_STATUS is not equal to 2
I don't want to do it from Model or View, but in the Controller or Repository.
Probably putting it before countryRepo.AddCountry(countries) in the Controller.
Create a Method In your Country Repository
public bool IsNameExist(string name, int id)
{
var result =entity.COUNTRIES.Any(c => c.COUNTRY_NAME == name && c.ACTION_STATUS != 2 && && c.COUNTRY_ID != id);
return result;
}
Then In your controller
public ActionResult Create(FormCollection collection, CountriesViewModel countries)
{
.......
if (countryRepo.IsNameExist(countries.COUNTRY_NAME, countries.COUNTRY_ID))
{
ModelState.AddModelError("COUNTRY_NAME", "COUNTRY NAME already exist.");
}
........
}

C#.NET MVC Route Aliasing

I've been searching and searching for away to make old URL like we used to do in aspx pages where you could have an alias pointing to a page like www.domain.com/my-great-alias point to www.domain.com/alias.aspx. I want to do the same thing in MVC but can not figure out how to make this happen in the route table. Where www.domain.com/my-great-alias would show up to the end user as such but point to www.domain.com/alias/2
Does this make sense to anyone else what I'm looking for?
// router
routes.MapRouteLowercase(
"Alias",
"{id}",
new
{
controller = "alias",
action = "select",
id = UrlParameter.Optional
}
);
// Alias controller
public ActionResult Select()
{
return View("select");
}
// Recipe Controller
public ActionResult Select()
{
return View();
}
You should be able to do this utilizing route config and parameters (as long as it's in the same domain):
Routing
routes.MapRoute(
name: "AliasRoute",
url: "{id}",
defaults: new { controller = "Alias" }
);
Controller
public class AliasController : Controller
{
public ActionResult Index(string id)
{
//DO SOME DATABASE STUFF HERE TO LOOKUP THE CORRESPONDIND CONTROLLER AND ACTION
var controllerAction = lookupControllerActionInDatabase(id);
return View(controllerAction.ViewName);
//OR
//DO CONDITIONAL CHECKS HERE AND RETURN THE APPROPRIATE VIEW
if (id == "my-great-alias") {
return View("Alias");
} else if (id == condition1) {
return View("viewForCondition1");
} else if (id == condition2) {
return View("viewForCondition2");
}
//AND SO ON...
}
}

Url.routeUrl returns null (I've tried same questions)

Hi everyone I wanna get url like {Controller}/{action}/{postid}-{address}
but routUrl returns null please help me to solve it.(I'm newbie in MVC)
my route config is
routes.MapRoute(
name: "Post",
url: "Posts/Show/{postid}-{address}",
defaults: new { controller = "Posts", action = "Index", postid = "", address = "" }
);
and index.cshtml
#item.PostTitle
the url that generate is
http://localhost:59066/Posts/Show/1-Post-with-Featured-Image
but in PostsController
public ActionResult Show(string add)
{ return View();}
"string add" is null !
I wouldn't change the Routes...
try this...
#item.PostTitle
This will send PostId and Address as parameters, so you can get them in the controller like:
public ActionResult AwesomeThings(int PostId, String Address)
{
var foo = PostId;
var bar = Address;
return View(model);
}
No changes in routing,
Index.cshtml:
#item.PostTitle
Controller:
public ActionResult Show(string postid, string address)
{ return View();}
I changed the route to
routes.MapRoute("Post", "post/{postid}-{address}", new { controller = "Posts", action = "Show" ,postid="",address=""}, namespaces);
and added a route with same controller and action
routes.MapRoute("PostAddress", "post/{IdAndAdd}", new { controller = "Posts", action = "Show" }, namespaces);
routes.MapRoute("Post", "post/{postid}-{address}", new { controller = "Posts", action = "Show" ,postid="",address=""}, namespaces);
then action received "idAndAdd" correctly
public ActionResult Show(string idAndAdd)
{
var parts = SeperateAddress(idAndAdd);
if (parts == null)
return HttpNotFound();
var post = db.Posts.Find(parts.Item1);
if (post == null)
return HttpNotFound();
if (!post.Address.Equals(parts.Item2, StringComparison.CurrentCultureIgnoreCase))
return RedirectToRoutePermanent("Post", new { postid = parts.Item1, address = post.Address });
return View(post);
}
and it's worked .

MVC4 Razor - Trying to get id in url for a blog post

All Im trying to do is get my url to have the blogid appending to it much like the following...
http://localhost/blog/blogpost/17
Here is my Controller...
public ActionResult BlogList(){ return View(_repository); }
public ActionResult BlogPost(string id)
{
ViewData["id"] = id;
if (ModelState.IsValid)
{
return RedirectToAction("BlogPost", new { id = id });
}
return View(_repository);
}
Now here is my route.config maproute
routes.MapRoute(
"MyBlog", // Route name
"blog/{action}/{id}", // URL with parameters
new { controller = "Blog", action = "blogpost", id =
UrlParameter.Optional } // Parameter defaults
);
Now I can get the url to appear when I click on a blog in the blogList. The page doesn't display the blog it displays a redirect loop message. If I omit the following code ...
if (ModelState.IsValid)
{
return RedirectToAction("BlogPost", new { id = id });
}
then I can display the blog. The url wont have the id value. Like this...
http://localhost/blog/blogpost/
What am I doing wrong?
The following code should work with your route:
// http://localhost/blog/bloglist
public ActionResult BlogList()
{
return View(_repository); // show all blog posts
}
// http://localhost/blog/blogpost/1
public ActionResult BlogPost(int? id = null)
{
if (id.HasValue == false || id.Value < 1)
{
// redirect to 404 page or BlogList
throw new NotImplementedException();
}
var blogPostObj = _repository.Find(id.Value);
if (blogPostObj == null)
{
// again redirect to 404
throw new NotImplementedException();
}
return View(blogPostObj);
}
Remove the BlogList() which takes 0 parameter
public ActionResult BlogList(){ return View(_repository); }
This is not required, since your id is type of string which can be null
The below code can be help you
public ActionResult BlogPost(string id)
{
var model=new ModelObject();
if(id!=null)
{
var model=Blogs.Find(id); //find it from repo
return View(model);
}
return View(model);
}
From your code it does not look like the id field is optional. Therefore I would change the route.
routes.MapRoute(
"MyBlog", // Route name
"blog/blogpost/{id}", // URL with parameters
new { controller = "Blog", action = "blogpost" },
new { id = #"(\d)+"} //ensures value is numeric.
);
RouteData.Values["id"] + Request.Url.Query

Custom routes with ASP.NET MVC for faceted search [ from QueryString to Route ]

I'm implementing a faceted search functionality where the user can filter and drill down on 4 properties of my model: City, Type, Purpose and Value.
I have a view section with the facets like this:
Each line displayed in the above image is clickable so that the user can drill down and do the filtering...
The way I'm doing it is with query strings that I pass using a custom ActionLink helper method:
#Html.ActionLinkWithQueryString(linkText, "Filter",
new { facet2 = Model.Types.Key, value2 = fv.Range });
This custom helper keeps the previous filters (query string parameters) and merges them with new route values present in other action links. I get a result like this when the user has applied 3 filters:
http://leniel-pc:8083/realty/filter?facet1=City&value1=Volta%20Redonda&
facet2=Type&value2=6&facet3=Purpose&value3=3
It's working but I'd like to know about a better/cleaner way of doing this using routes. The order of the parameters can change depending on the filters the user has applied. I have something like this in mind:
http://leniel-pc:8083/realty/filter // returns ALL rows
http://leniel-pc:8083/realty/filter/city/rio-de-janeiro/type/6/value/50000-100000
http://leniel-pc:8083/realty/filter/city/volta-redonda/type/6/purpose/3
http://leniel-pc:8083/realty/filter/type/7/purpose/1
http://leniel-pc:8083/realty/filter/purpose/3/type/4
http://leniel-pc:8083/realty/filter/type/8/city/carangola
Is this possible? Any ideas?
Is this possible? Any ideas?
I would keep the query string parameters for filtering.
But if you wanted to achieve the urls you have asked for in your question I will cover 2 possible techniques.
For both approaches that I will present here I assume that you already have a view model:
public class FilterViewModel
{
public string Key { get; set; }
public string Value { get; set; }
}
and a controller:
public class RealtyController : Controller
{
public ActionResult Filter(IEnumerable<FilterViewModel> filters)
{
... do the filtering ...
}
}
The first option is to write a custom model binder that will be associated with the IEnumerable<FilterViewModel> type:
public class FilterViewModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var filtersValue = bindingContext.ValueProvider.GetValue("pathInfo");
if (filtersValue == null || string.IsNullOrEmpty(filtersValue.AttemptedValue))
{
return Enumerable.Empty<FilterViewModel>();
}
var filters = filtersValue.AttemptedValue;
var tokens = filters.Split('/');
if (tokens.Length % 2 != 0)
{
throw new Exception("Invalid filter format");
}
var result = new List<FilterViewModel>();
for (int i = 0; i < tokens.Length - 1; i += 2)
{
var key = tokens[i];
var value = tokens[i + 1];
result.Add(new FilterViewModel
{
Key = tokens[i],
Value = tokens[i + 1]
});
}
return result;
}
}
which will be registered in Application_Start:
ModelBinders.Binders.Add(typeof(IEnumerable<FilterViewModel>), new FilterViewModelBinder());
and you will also have a filter route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Filter",
"realty/filter/{*pathInfo}",
new { controller = "Realty", action = "Filter" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
A second possibility is to write a custom route
public class FilterRoute : Route
{
public FilterRoute()
: base(
"realty/filter/{*pathInfo}",
new RouteValueDictionary(new
{
controller = "realty", action = "filter"
}),
new MvcRouteHandler()
)
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
var filters = rd.Values["pathInfo"] as string;
if (string.IsNullOrEmpty(filters))
{
return rd;
}
var tokens = filters.Split('/');
if (tokens.Length % 2 != 0)
{
throw new Exception("Invalid filter format");
}
var index = 0;
for (int i = 0; i < tokens.Length - 1; i += 2)
{
var key = tokens[i];
var value = tokens[i + 1];
rd.Values[string.Format("filters[{0}].key", index)] = key;
rd.Values[string.Format("filters[{0}].value", index)] = value;
index++;
}
return rd;
}
}
which will be registered in your RegisterRoutes method:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("Filter", new FilterRoute());
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
In my opinion (and this is pretty subjective) your initial approach seems fine. I think search criteria belong on the querystring as they represent a subset of the resources you're trying to retrieve.
Your urls don't make much sense from a logical resource hierarchy point of view.
I would probably rename the "filter" method "search" however, with the filters being the querystring variables. Also, is it necessary to define facets in the querystring - can't you achieve the same result by naming the facet explicity, like ?city=Volta&type=6&purpose=3 ?

Resources