I'm just starting out with C# and ASP.NET and have the following questions. I am working with code adapted from a couple different tutorials playing with Northwind and have gotten this far. The list of acceptable categories is currently hard coded in a string but I would like to look up the CategoryName in the database to verify that it exists.
Obviously the purpose of this is to ensure that users don't just type:
www.domain.com/Categories/AnyUrlWillWork and return a valid page.
Also does anyone have an tips of how they are dealing with capitalization issues since the routing is case sensitive? For example Categories/beverages should forward to Categories/Beverages ?
Thanks in advance for any assistance, and glad to be joining Stack Overflow.
//Complex contraint class
public class EchoConstraint : IRouteConstraint
{
public readonly string[] ValidMessages = { "Beverages", "Produce", "Confections", "Seafood" };
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
string message = values["CategoryName"] as string;
return ValidMessages.Contains(message);
}
}
//Routes
RouteTable.Routes.MapPageRoute(
"Category Route", // Route name
"Categories/{CategoryName}", // Url pattern
"~/ShowProductsInCategory.aspx", // Physical file
true,
new RouteValueDictionary
{{"CategoryName", "Beverages"}}, //Sets default value if none is provided in URL
new RouteValueDictionary
{{"CategoryName", new EchoConstraint()}}
);
Is this MVC? If so, why not route to a function, which will check the category name against your data store and redirect to an error page if such category doesn't exist?
public ActionResult Index(string catName)
{
if (string.IsNullOrEmpty(catName) || !MyCategoriesDataStore.Exists(catName))
return RedirectToAction("CategoryError");
// Load category data to be used from View
return View();
}
A WebForms solution would be:
public class EchoConstraint : IRouteConstraint
{
private IRepository rep;
public EchoConstraint(IRepository r) { rep = r; }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return rep.GetCategory(message) == 0;
}
}
.
.
.
new RouteValueDictionary
{{"CategoryName", new EchoConstraint(myDataAccessRepo)}}
);
Where you pass an object of class implementing IRepository with your data access logic (using NHibernate, EntityFramework or your own DAL implementation). You need to return a bool value, and this is what I did.
Related
I want to set up a route that will match on any url, with a constraint. This is what I have tried:
routes.MapRouteLowercase("CatchAll Content Validation", "{*url}",
new { controller = "Content", action = "LoadContent" },
new { url = new ContentURLConstraint(), }
);
and for testing purposes I have the following simple constraint:
public class ContentURLConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var s = (string)values["url"];
return s.Contains("comm");
}
}
If the constraint is satisfied I when expect to pass the full url to the controll action LoadContent
public ActionResult LoadContent(string url)
{
return Content(url);
}
I am expecting, when I load a page, for s in the Match function to contain the entire url, so that I can check it, but instead it is null. What might I be missing?
You get null when there is no url in the route, i.e. host itself: http://localhost:64222.
When there is a url, e.g.: http://localhost:64222/one/two, your url parameter will hold a value: one/two.
You can modify your constraint to something like:
public class ContentURLConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values routeDirection)
{
var url = values[parameterName] as string;
if (!string.IsNullOrEmpty(url))
{
return url.Contains("comm");
}
return false; // or true, depending on what you need.
}
}
What might I be missing?
Could be that you are missing that url constraint does not contain host, port, schema and query string. If you need it - you can get it from httpContext.Request.
I would like to create attribute routing in MVC 5 application with one simple constraint:
public class UserAgentConstraint : IRouteConstraint
{
private string _requiredUserAgent;
public UserAgentConstraint(string requiredUserAgent)
{
_requiredUserAgent = requiredUserAgent;
}
public bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
return httpContext.Request.UserAgent != null &&
httpContext.Request.UserAgent.Contains(_requiredUserAgent);
}
}
In MVC 4 i was able to register it in this way:
routes.MapRoute("ChromeRoute", "{*catchall}",
new { controller = "Home", action = "Index" },
new { customConstraint = new UserAgentConstraint("Chrome") });
How can i achieve same result using only attribute routing?
I know that i can register constraint in this way:
var constraintsResolver = new DefaultInlineConstraintResolver();
constraintsResolver.ConstraintMap.Add("UserAgent", typeof(UserAgentConstraint));
But how to add it to my route pattern? Something like this:
[Route("Home/Index:UserAgent(Chrome)")]
public ActionResult Index() {}
doesnt work
Review aspnetmvc source code,It can be helpful , try to review
RouteContraintAttribute , AreaAttribute and RouteAttribute
For example RouteConstraintAttribute:
https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Core/RouteConstraintAttribute.cs
I'm trying to figure out how to do that.
Attribute routing allows the following constraints to be applied and is used via this format {parameter:constraint} eg [Route("Index/{id:int}")]. So your attribute should be [Route("Home/{Index:UserAgent(Chrome)}")]
{x:alpha}
{x:bool}
{x:datetime}
{x:decimal}
{x:double}
{x:float}
{x:guid}
{x:int}
{x:length()}
{x:long}
{x:max()}
{x:maxlength()}
{x:min()}
{x:minlength()}
{x:range()} {x:regex()}
I am new to web api coming from a WCF background and as prep I watched Shawn Wildermuth's Pluralsight course on the subject before diving in. His course material was designed around more traditional routing. One of the subjects the course dives into is HATEOAS and how easy it is to achieve this with a base api controller and model factory.
One of the first things I hit when implementing against attribute routing was the need for the UrlHelper to have a route name as the first argument of the Link() method, something that was inherited in the conventional routing configured in the WebApiConfig.cs.
I worked around this by decorating one of my controllers route attributes with the Name property and it appears that all methods in that controller have access to the name property regardless of which method I put it on (see code below). While I find this a bit odd, it works. Now that I had HATEOAS implemented, I noticed the URL's it was generating were in the query string format and not "url" formatted (I know the term is wrong but bear with me). Instead of .../api/deliverables/1 I am getting .../api/deliverables?id=1.
This is "ok" but not the desired output. While I still have not figured out how to adjust the formatting the of the return value of the URL, I figured I would test the query string against my controller and found that in the query string format my controller does not work but in the "url" format it does.
I then spent an hour trying to figure out why. I have attempted different decorations (i.e. [FromUri] which from my reading should only be necessary for complex objects which default to the message body) to setting default values, constraints and making it optional (i.e. {id?}).
Below is the code in question, both for the controller, the base api controller and the model factory that makes the HATEOAS implementation possible.
The 3 questions I have are:
1) How to make the controller accept the "id" on the querystring AND in the url format (.../deliverables/1 and .../deliverables?id=1.
2) How to make the Link method of the URL helper return the value in the url format (it is currently returning it as a query string.
3) Proper way to name routes in WebAPI 2. What I am doing (assigning a name to a single method and the others appear to inherit it simply smells and I have to believe this would crumble as I actually start to implement more complex code. Is Shawn's implementation flawed in some way? I like not having to hard code a URL for test/development purposes but maybe UrlHelper is not the best way to achieve this. It seems to carry with it a lot of baggage that may not be necessary.
Controller:
[RoutePrefix("api/deliverables")]
public class DeliverablesController : BaseApiController
{
private readonly IDeliverableService _deliverableService;
private readonly IUnitOfWork _unitOfWork;
public DeliverablesController(IDeliverableService deliverableService, IUnitOfWorkAsync unitOfWork)
{
_deliverableService = deliverableService;
_unitOfWork = unitOfWork;
}
[Route("", Name = "Deliverables")]
public IHttpActionResult Get()
{
return Ok(_deliverableService.Get().Select(TheModelFactory.Create));
}
[Route("{id}")]
public IHttpActionResult Get(int id)
{
return Ok(TheModelFactory.Create(_deliverableService.Find(id)));
}
[Route("")]
public IHttpActionResult Post([FromBody]DeliverableModel model)
{
try
{
var entity = TheModelFactory.Parse(model);
if (entity == null)
{
return BadRequest("Could not parse Deliverable entry in body.");
}
_deliverableService.Insert(entity);
_unitOfWork.SaveChanges();
return Created(Request.RequestUri + "/" + entity.Id.ToString(CultureInfo.InvariantCulture),TheModelFactory.Create(entity));
}
catch (Exception exception)
{
return BadRequest(exception.Message);
}
}
}
Base API Controller:
public abstract class BaseApiController : ApiController
{
private ModelFactory _modelFactory;
protected ModelFactory TheModelFactory
{
get
{
return _modelFactory ?? (_modelFactory = new ModelFactory(Request));
}
}
}
Model Factory:
public class ModelFactory
{
private readonly UrlHelper _urlHelper;
public ModelFactory(HttpRequestMessage request)
{
_urlHelper = new UrlHelper(request);
}
public DeliverableModel Create(Deliverable deliverable)
{
return new DeliverableModel
{
Url = _urlHelper.Link("deliverables", new { id = deliverable.Id }),
Description = deliverable.Description,
Name = deliverable.Name,
Id = deliverable.Id
};
}
public Deliverable Parse(DeliverableModel model)
{
try
{
if (string.IsNullOrEmpty(model.Name))
return null;
var entity = new Deliverable
{
Name = model.Name,
Description = !string.IsNullOrEmpty(model.Description)
? model.Description
: string.Empty
};
return entity;
}
catch (Exception)
{
return null;
}
}
}
As a point of clarification, non-attribute (traditional) routing works without an issue for both the URI and query string formats:
config.Routes.MapHttpRoute(
name: "deliverables",
routeTemplate: "api/deliverables/{id}",
defaults: new { controller = "deliverables", id = RouteParameter.Optional }
);
In my opinion, this is one of the problems with Attributed routing. That's why I use it for exceptional cases only. I use route tables for the majority of routing then drop down into attributed routing for exceptional cases.
To solve this your way, have you thought about multiple routes on the Get(id)? (I don't actually think this would work, but its worth a try).
I have this simple User Area in my MVC 4 project.
public class UserAreaRegistration : AreaRegistration
{
public override string AreaName { get { return "User"; } }
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute("User_Constraint",
"{userName}/{controller}/{action}/{id}",
new { userName = string.Empty, controller = "Products", action = "Index", id = UrlParameter.Optional },
new { userName = new UserNameRouteConstraint() },
new[] { "T2b.Web.Areas.User.Controllers" }
);
}
}
To make sure the User Name exists I have a RouteConstraint called UserNameRouteConstraint()
All this does is a simple lookup in my users table and return true if the user has been found.
So far so good, this construction works fine!
Now; My view in the User Area has the following line of code
#Html.ActionLink("More information", "details", new {id = product.Guid})
This single line causes the UserNameRouteConstraint() to be called....
How and why!? If I write the link in plain HTML (see example below) it works well, but I want to keep to the MVC Principles as close as possible.
More information
Is there any way to prevent the RouteConstraint call?
Whenever routes are generated the constraints are processed.
You can add this check to stop the constraint depending on whether the constraint is handling an incoming request or generating a URL from a function like ActionLink:
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if(routeDirection == RouteDirection.UrlGeneration)
return false;
...
}
When you call ActionLink behind the scenes it creates a RouteValueDictionary and runs RouteCollection.GetVirtualPath(). This part is not open source but my best guess as to how it works is that it checks the parameters of the generated route value dictionary against the defaults and constraints of each route until it finds one that matches. Because of this it runs your constraints, and you should want it to run your constraints so that it doesn't end up matching to the wrong route.
Is it possible, from within ASP.NET MVC, to route to different controllers or actions based on the accessing device/browser?
I'm thinking of setting up alternative actions and views for some parts of my website in case it is accessed from the iPhone, to optimize display and functionality of it. I don't want to create a completely separate project for the iPhone though as the majority of the site is fine on any device.
Any idea on how to do this?
Mix: Mobile Web Sites with ASP.NET MVC and the Mobile Browser Definition File
Don't know if the above helps as I havn't watched it yet.
And this one;
How Would I Change ASP.NET MVC Views Based on Device Type?
You can create a route constraint class:
public class UserAgentConstraint : IRouteConstraint
{
private readonly string _requiredUserAgent;
public UserAgentConstraint(string agentParam)
{
_requiredUserAgent = agentParam;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.UserAgent != null &&
httpContext.Request.UserAgent.ToLowerInvariant().Contains(_requiredUserAgent);
}
}
And then enforce the constraint to one of the routes like so:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional},
constraints: new {customConstraint = new UserAgentConstraint("Chrome")},
namespaces: new[] {"MyNamespace.MVC"}
);
You could then create another route pointing to a controller with the same name in another namespace with a different or no constraint.
Best bet would be a custom action filter.
All you have to do is inherit from ActionMethodSelectorAttribute, and override the IsValidRequest class.
public class [IphoneRequest] : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
// return true/false if device is iphone
Then in your controller
[IphoneRequest]
public ActionResult Index()