MVC Routes - How to get a URL? - c#

In my current project we have a notification system. When an oject is added to another objects collection, an email is sent to those who are subscibed to the parent object. This happens on the object layer and not in the View or Controller.
Here's the problem:
Although we can say who created what with what information in the email, we cannot embed links to those objects in the email because in the object layer there is no access to a UrlHelper. To construct a UrlHelper you need a RequestContext, which again does not exist on the object layer.
Question:
I want to make a helper class to create the url's for me. How can I create an object that will generate these urls without a request context? Is it possible?

The problem is compounded by the fact that you don't want a relative URL in an email, you want an absolute email so you need to hard-code the domain too because there is no request to grab it from.
Another factor is that emails can outlive the current site structure by months or years so you need a kind of permalink, and thus a way to associate multiple Urls with a single action (additional routes). This latter issue is also a factor in SEO where you don't want to leave any page behind.
For now a static method on your controller UrlToActionX(params) sitting next to the method ActionX seems like the simplest workaround. All it does is the appropriate string.Format(...) on the id's of the strongly-typed parameters to generate the permanent Url. Add a static domain on the front, or a domain from the user object (since you know which domain they visit when they come to your site) and you have your email link.
It's not ideal but at least you now have only one place to maintain the Url generation.
IMHO: When it comes to permanent links to a changing web site sometimes it's better to rely on "configuration over convention". :-)

I'm not aware of a way to do this, you MUST have access to the routes at the very least to make your own helper. Unless your business objects know about the registered routes, you can't get away from doing some hard-coding.
Here is how you might limit the hard-coding of urls though...
Code in a url with all the relevant bits in your object's methods..
class Event
{
public void SendEmail()
{
var url = string.Format("http://myurl.com/r/Event?eventId={0}", EventId);
//send emails...
}
}
Note the /r/Event piece of the url. This would be a map to a RController that would be responsible for taking arbitrary, made-up links and sending a 301 Permanent Redirect and going through the route engine to create a real url using the current routes. This way you are only hard-coding a utility controller url and not to the ever evolving controller actions of your real pages.
class RController : Controller
{
public ActionResult Event(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Details", "Event", new { eventId = eventId });
return null;
}
public ActionResult Register(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Register", "Event", new { eventId = eventId });
return null;
}
}
It just feels a bit better than hard-coding a bunch of different controllers/actions that you might decide to rename later. Think of it as your own little TinyUrl like service.

You could define an interface with a method that takes whatever information is necessary to create a URL (object ids or whatever) and returns a URL. Write an implementation of that interface that uses the UrlHelper to do this work, and then supply this to your object layer (ideally with an IoC container).

You could use:
VirtualPathUtility.ToAbsolute(string.Format("~/r/Event?eventId={0}", id))
to resolve the url. Still not nice though.

Related

How to Register Custom IRouter in .Net 7 MVC Application?

I have a custom IRouter implementation and I can't figure out how to register it in a .Net 7 MVC application.
What I am trying to accomplish is this: Incoming requests have the form of https://example.com/{id} and when such a request comes in I need to hit the database to retrieve the controller and action for that {id}, do some checks on it and if everything looks right pass the request on to the default router along with the entire RequestContext. The reason behind that is that such an url is valid only for a given time and a subset of visiting users. Also the underlying action and controller must not be guessable by looking at the url.
What I came up with is a cutom IRouter implementation (which also allows me to create those Urls) but I can't seem to figure out how to register on application startup.
Is using a custom IRouter still the right approach in .Net 7? How do I register one? Or am I totally on the wrong track?
One option is to switch back from endpoint routing:
builder.Services.AddMvc(options => options.EnableEndpointRouting = false);
app.UseMvc(routeBuilder => routeBuilder.Routes.Add(new CustomRouter(routeBuilder.DefaultHandler)));
UPD
Alternative approach is to use MapDynamicControllerRoute. Something along this lines:
builder.Services.AddScoped<MyDynamic>();
// ...
app.MapDynamicControllerRoute<MyDynamic>("/{*internalid}");
public class MyDynamic: DynamicRouteValueTransformer
{
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
if (values.TryGetValue("internalid", out var value) && value is string s && s == "777") // simulate some search for id
{
values["controller"] = "Home";
values["action"] = "test";
}
return new ValueTask<RouteValueDictionary>(values);
}
}
Note that since you are still using the default routing pipeline this looks like security through obscurity which in general is not a good approach and probably you should better implement appropriate security restrictions (you should not rely on the "impossibility" to guess the actual routes).

Maintain the model lifetime in MVC application

I am new to MVC and I have very simple problem.
When user login to my application I need to create a specific object (model) for the user for eg UserObject.
This object is unique to current logged in user and should only be disposed when user click on logout.
I don’t know how to maintain the lifetime of the object. As if I create object in Action method of controller class then as soon as the request is finished I lose the reference of the object.
How this should have been done?
The lifetime of your models are only going to be as long as the request. So each time the user goes to another page or refreshes, the MVC framework is going to instantiate a new controller (and model within). Otherwise your server would have a ton of static objects floating around in memory which would use up a lot of resources and wouldn't scale.
In order to manage state, you are going to need to use other methods such as sessions/cookies and a database.
So let's say the user logs in via /User/Login. This routes the request to an action named UserController.Login().
Inside this action, it instantiates a UserModel.
public ActionResult Login(string username, string password) {
var userModel = new UserModel();
if (userModel.Authenticate(username, password)) {
// Setup your session to maintain state
Session["username"] = username;
} else {
return View("Login");
}
return View("LoginComplete");
}
You might want the user model to actually create the session, but I've shown it here for clarity.
The user model authenticates the user, and then you create a session just like you would in a traditional non-MVC site.
Then in subsequent requests, you will want to authorize the user, and use any session data you have to retrieve state information.
public ActionResult SuperSecretPlace() {
var userModel = new UserModel();
string username = Session["username"]
var user = userModel.GetUserByUsername(username);
if (user == null) throw new HttpException(401, "User is not authorized.");
return View("SuperSecretPlace", user);
}
In the action above, the UserModel might do something like query a database to retrieve the user's data so you can pass it in to the corresponding view.
If you want to make life easier, you might want to just use .NET's built in forms authentication:
http://www.codeproject.com/Articles/578374/AplusBeginner-splusTutorialplusonplusCustomplusF
For more info about the lifecycle of MVC:
http://www.dotnet-tricks.com/Tutorial/mvc/TbR0041112-Asp.net-MVC-Request-Life-Cycle.html
http://www.asp.net/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5-application
Actually what you are trying to achieve is passing model from controller to controller which is not possible. When an action is executed the context of the model object is disposed at the view and it can cannot be passed from controller to controller. You have to create a new object repopulate it and use it to achieve the goal in different controller.If you need the data to be persisted you can use sessions but still you need to create an object of the model in every controller.
The following image is for your reference as to see what to use when passing data between model-view-controller. Please feel free to ask if you need more information on this.
As opposed to the other aswers I would not use session as it has quite some disadvantages (scalability, pessimistic concurrency which blocks concurrent calls, app pool recycling...). Why you should not use session is documented in a lot of places like here or here.
Instead, I would store it in a cookie.
However, be sure to not store confidential or sensitive data. Whatever you use (cookies or session), it can be tampered with or stolen. If you are dealing with sensitive information, you need other solutions. Read also more about secure cookie solution here.

Stackoverflow style URL (customising outgoing URL)

If I navigate to the following stackoverflow URL http://stackoverflow.com/questions/15532493 it is automatically appended with the title of the question like so:
http://stackoverflow.com/questions/15532493/mvc-custom-route-gives-404
That is, I can type the URL into my browser without the question title and it is appended automatically.
How would I go about achieving the same result in my application? (Note: I am aware that the question title doesn't affect the page that is rendered).
I have a controller called Users with an action method called Details. I have the following route defined:
routes.MapRoute("UserRoute",
"Users/{*domain}",
new { controller = "User", action = "Details" },
new { action = "^Details$" });
As this is an intranet application the user is authenticated against their Windows account. I want to append the domain and user name to the URL.
If I generate the URL in the view like so:
#Html.ActionLink("Model.UserName", "Details", "User", new { domain = Model.Identity.Replace("\\", "/") })
I get a URL that look like this:
Domain/Users/ACME/jsmith
However, if the user navigates to the URL Domain/Users/ by using the browsers navigation bar it matches the route and the user is taken to the user details page. I would like to append the ACME/jsmith/ onto the URL in this case.
The research I have done so far indicates I might have to implement a custom route object by deriving from RouteBase and implementing the GetRouteData and GetVirtualPath methods but I do not know where to start with this (the msdn documentaiton is very thin).
So what I would like to know is:
Is there a way of achieving this without implementing a custom route?
If not, does anyone know of any good resources to get me started implementing a custom route?
If a custom route implementation is required, how does it get at the information which presumably has to be loaded from the database? Is it OK to have a service make database calls in a route (which seems wrong to me) or can the information be passed to the route by the MVC framework?
It's actually pretty simple. Since the title is just there for SEO reasons you do not need to get to the actual question, so the Question controller (in SO case) will load the correct question based on the id (in the URL) and redirect the user with a 301 status code.
You can see this behavior with any web inspector
You could do it client-side with Javascript:
history.pushState({}, /* Title Here */, /* URL Here */ );
Only downside is not all browsers support it.

"True" REST routing via MVC 4 Web API

TL;DR Summary: Can I configure MVC Web API routing for HTTP GET, PUT & DELETE?
I've been looking into replacing our old Data Access Layer (a DLL based on DataSets and TableAdapters) with a private API, with a view to creating a public API if it's successful. I've done some work with MVC 4 to refresh our frontend, and loved working with it, so it seems sensible to explore the "Web API" project type before diving into WS- or WCF-based libraries.
An initial demo allows me to return XML/JSON nicely, for example:
//service.url/api/Users
... returns a list of users, while a specific user's details can be accessed via:
//service.url/api/Users/99
So far, so RESTful. However, in order to truly map URIs to resources I want to do an HTTP PUT (new user) or HTTP DELETE (remove user) to the the URI listed above. In all of the examples I've seen for these projects, along with the Scaffolds provided in Visual Studio, this convention is followed:
//service.url/api/Users/Create
//service.url/api/Users/Delete/99
//service.url/api/Users/Update/99
... and so on. This feels like side-stepping the issue to me, which is a shame when what's there has been put together so nicely!
Any thoughts on how best to approach this?
What you want is the default in MVC Web API. I'm not sure what you are looking at but here is a great example of routing the Get/Post/Put/Delete to actions.
For example you may want:
public class UsersController : ApiController
{
// GET http://service.url/api/Users/1
[HttpGet]
public User GetUser(int id);
// POST http://service.url/api/Users/?name=richard...
[HttpPost]
public User AddUser(User model);
// PUT http://service.url/api/Users/?id=1&name=Richard...
[HttpPut]
public User UpdateUser(User model);
// DELETE http://service.url/api/Users/1
[HttpDelete]
public User DeleteUser(int id);
}
I've explicitly set these, but the GetUser and DeleteUser don't need the prefix because they start with the matching HTTP method.
The link provided by Erik is a good start, but I see how it can confuse the situation when looking for a simple RESTful API that makes use of the HTTP verbs to perform these CRUD actions. If you're looking to use the HTTP verbs of GET, PUT, POST, and DELETE (and possibly PATCH, but I'm not covering that here) and you're ok with using convention, then the following would work:
public class UsersController : ApiController
{
// GET http://service.url/api/Users
public User GetAllUsers(){ ... }
// GET http://service.url/api/Users/1
public User GetUser(int id){ ... }
// POST http://service.url/api/Users/
// User model is passed in body of HTTP Request
public User PostUser([FromBody]User model){ ... }
// PUT http://service.url/api/Users/1
// User model is passed in body of HTTP Request
public User PutUser(int id, [FromBody]User model){ ... }
// DELETE http://service.url/api/Users/1
public User DeleteUser(int id){ ... }
}
Note that the attributes on the method are not needed when using the HTTP verb action convention in Web API. Also, note that I use the [FromBody] attribute on the User parameter for POST and PUT to denote that the body contains the data I wish to send. This may not be most convenient for POST if you're trying to append to a resource, and I have not tried creating/modifying data through query parameters using Web API. It certainly makes the call feel very clean to place your data in the body. "POST/PUT this content in the body at this resource."
Also, the way I read PUT in the spec, and I could very well be wrong, is that it acts as a replace. That also makes sense given the last line above. I'm PUTting this resource in this location, replacing what was already there. The spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) states: "If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server." The term they use is "modified" so I guess that leaves enough room for interpretation for the end user. That's where PATCH comes in (https://www.rfc-editor.org/rfc/rfc5789), but I don't have enough information to comment on that at this time.

Change ASP.NET MVC Routes dynamically

usually, when I look at a ASP.Net MVC application, the Route table gets configured at startup and is not touched ever after.
I have a couple of questions on that but they are closely related to each other:
Is it possible to change the route table at runtime?
How would/should I avoid threading issues?
Is there maybe a better way to provide a dynamic URL? I know that IDs etc. can appear in the URL but can't see how this could be applicable in what I want to achieve.
How can I avoid that, even though I have the default controller/action route defined, that default route doesn't work for a specific combination, e.g. the "Post" action on the "Comments" controller is not available through the default route?
Background: Comment Spammers usually grab the posting URL from the website and then don't bother to go through the website anymore to do their automated spamming. If I regularly modify my post URL to some random one, spammers would have to go back to the site and find the correct post URL to try spamming. If that URL changes constantly I'd think that that could make the spammers' work more tedious, which should usually mean that they give up on the affected URL.
I would consider to implement my own IRouteHandler and put some custom logic in my custom ControllerActionInvoker. How it would work ? The route table wouldn't dynamically change but you could check in your custom ControllerActionInvoker for a random parameter in the route path and invoke or not the corresponding action.
My route :
routes.Add
(
new Route
(
"blog/comment/{*data}",
new RouteValueDictionary(new {controller = "blog", action = "comment", data = ""}),
new MyRouteHandler()
)
);
My I route handler :
class MyRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyHttpHandler(requestContext);
}
}`
My handler :
class MyHttpHandler : MvcHandler
{
public MyHttpHandler(RequestContext requestContext) : base(requestContext)
{
}
protected override void ProcessRequest(HttpContextBase httpContext)
{
IController controller = new BlogController();
(controller as Controller).ActionInvoker = new MyActionInvoker();
controller.Execute(RequestContext);
} }`
and my action ivoker where the custom logic for handling an action or not should be coded :
class MyActionInvoker : ControllerActionInvoker
{
protected override ActionResult InvokeActionMethod(MethodInfo methodInfo, IDictionary<string, object> parameters)
{
var data = ControllerContext.RouteData.GetRequiredString("data");
// put my custom logic to check whetever I'll handle the action or not. The data could be a parameter in the database for that purpose.
return base.InvokeActionMethod(methodInfo, parameters);
}
}
I don't know it it's the best solution but for now it's the one that comes to my mind.
Considering the actual problem background, the usual approach is to include a dynamically created transaction number. It should be stored in a hidden form field as well as in the server side session dictionary and only be valid for exactly one request.
I think today a lot of frameworks provide such a security mechanism; whereas this attack type is known as Cross-Site-Request-Forgery (csrf).

Categories

Resources