Detect QueryString and create Session on any Request URL - asp.net-mvc

Currently, I'm detecting a QueryString and creating the session on the Action of the Controller. The query string can be coming in with the url to any deep page within the site. I don't want to code it in most of my Actions.
Therefore, I'm wondering, is there a generic way to detect QueryString in the URL in any request that comes to site's pages. I think I can create a 'BaseController' from the 'Controller' and change all my current Controller's to inherit from BaseController.
Is there any other ways? may be in RouteConfig.cs? Startup.Auth.cs?

(.Net Core) You can craete custom middleware to detect any request using Httpcontext.
read more about middleware
. example of custom middleware

Thanks KHAL, for your hint.
so in classic ASP.NET MVC, in Startup.Auth.cs file
app.Use(async (context, next) =>
{
var referral = context.Request.Query["referral"];
if(referral == "whatever")
{
//Do your thing ;)
}
// Call the next delegate/middleware in the pipeline
await next();
});
However, note that the position of this inside the Startup.Auth.cs file is critical, if you want to use Session. It cannot be at the top of the file

Related

.NET Core MVC get route matched for logging middleware

In my project I am logging every request using logging middleware. How can I get the route that was matched for the request for logging purposes?
I have the full path in the request e.g. /v1/User/123
But I want to log this: /v1/User/{id}
Here is what I have so far:
public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var path = context.ActionDescriptor.AttributeRouteInfo.Template;
await next();
}
This is in my base controller, how do I get this to the logging middleware?
Here is how I got it to the logging middleware:
BaseController:
public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var actionPath = context.ActionDescriptor.AttributeRouteInfo.Template;
HttpContext.Items.Add("ActionInfo", actionPath);
await next();
}
LoggingMiddleware:
var actionInfo = context.Items["ActionInfo"];
if (actionInfo != null)
{
actionMatched = actionInfo.ToString();
}
Is there a better way to do this?
Also this only works if you are using attribute routing. How can this work if you are registering routes on startup?
Well, plain and simple, you can't.
A route is an ASP.NET Core MVC concept and its existence can therefore only be found within the MVC middleware of ASP.NET Core. This is why you can access it from an ActionFilter, that's an ASP.NET Core MVC concept as well. From a piece of middleware, the closest you can get is by going to httpContext.Request and using Path/Query/QueryString/Method.
Knowing this, the choices you then have:
Implement your logger as an action filter. This allows you to easily access the route and other detailed bits of information like the controller and action method. But, requests that are handled outside of the MVC middleware (static file, authorization fails, name it) will not be logged.
Implement it as middleware and simply log the URL.
Store the data somehow, somewhere so that your middleware can access it, as you've done by adding items to the HttpContext. This feels quite hacky.
Option 1 and 2 are both fine choices and it depends on your needs which is best for you. Perhaps even both 1 and 2.

MVC Redirect to a different view in another controller

After a user has completed a form in MVC and the post action is underway, I am trying to redirect them back to another view within another model.
Eg. This form is a sub form of a main form and once the user has complete this sub form I want them to go back to the main form.
I thought the following might have done it, but it doesn't recognize the model...
//send back to edit page of the referral
return RedirectToAction("Edit", clientViewRecord.client);
Any suggestions are more that welcome...
You can't do it the way you are doing it. You are trying to pass a complex object in the url, and that just doesn't work. The best way to do this is using route values, but that requires you to build the route values specifically. Because of all this work, and the fact that the route values will be shown on the URL, you probably want this to be as simple a concise as possible. I suggest only passing the ID to the object, which you would then use to look up the object in the target action method.
For instance:
return RedirectToAction("Edit", new {id = clientViewRecord.client.ClientId});
The above assumes you at using standard MVC routing that takes an id parameter. and that client is a complex object and not just the id, in which case you'd just use id = clientViewRecord.client
A redirect is actually just a simple response. It has a status code (302 or 307 typically) and a Location response header that includes the URL you want to redirect to. Once the client receives this response, they will typically, then, request that URL via GET. Importantly, that's a brand new request, and the client will not include any data with it other than things that typically go along for the ride by default, like cookies.
Long and short, you cannot redirect with a "payload". That's just not how HTTP works. If you need the data after the redirect, you must persist it in some way, whether that be in a database or in the user's session.
If your intending to redirect to an action with the model. I could suggest using the tempdata to pass the model to the action method.
TempData["client"] = clientViewRecord.client;
return RedirectToAction("Edit");
public ActionResult Edit ()
{
if (TempData["client"] != null)
{
var client= TempData["client"] as Client ;
//to do...
}
}

Dynamic url rewriting with MVC and ASP.Net Core

I am re-writing my FragSwapper.com website (currently in Asp 2.0!) with ASP.Net Core and MVC 6 and I'm trying to do something I would normally have to break out a URL Re-Write tool along with db code and some Redirects but I want to know if there is a "better" way to do it in ASP.Net Core possibly with MVC Routing and Redirecting.
Here are my scenarios...
URL Accessing the site: [root]
What to do: Go to the usual [Home] Controller and [Index] View (no [ID]). ...it does this now:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
URL Accessing the site: [root]/ControllerName/...yada yada...
What to do: Go to the Controller, etc...all THIS works too.
The tricky one: URL Accessing the site: [root]/SomeString
What to do: Access the database and do some logic to decide if I find an Event ID. If I do I go to [Event] Controller and [Index] View and an [ID] of whatever I found. If not I try to find a Host ID and go to [Home] Controller and [Organization] View with THAT [ID] I found. If I don't find and Event or Host go to the usual [Home] Controller and [Index] View (no [ID]).
The big gotcha here is that I want to Redirect to one of three completely different views in 2 different controllers.
So the bottom line is I want to do some logic when the user comes to my site's root and has a single "/Something" on it and that logic is database driven.
If you understand the question you can stop reading now...If you feel the need to understand why all this logic is needed you can read on for a more detailed context.
My site has basically two modes: Viewing an Event and Not Viewing an
Event! There are usually 4 or 5 events running at an one time but
most users are only interested in one event but it's a DIFFERENT event
every 4 months or so..I have a [Host] entity and each Host holds up to
4 events a year, one at a time. Most users only care about one Host's
events.
I'm trying to avoid making a user always go to an event map and find
the event and click on it since I have a limit to how many times I can
show a map (for free) and it's really not necessary. 99.99% of the
time a user is on my site they are on an Event screen, not my Home
screens, and are interested in only one event at a time. In the
future I want to code it so if they come to my website they go right
to their event or a new event from their favorite host so I can avoid
a LOT of clicks and focus my [Home] controller pages for newbs...but I
don't have auto-login working yet so that's on the back burner.
But for now I want hosts to always have the same url for their events:
FragSwapper.com/[Host Abbreviation] ...and know it will always go to
their current event which has a different ID every 4 months!!!
Crazy...I know...but technically very easy to do, I just don't know
how to do it properly in MVC with how things are done.
Update: ASP.Net Core 1.1
According to the release notes, a new RewriteMiddleware has been created.
This provides several different predefined rewrite options and utility extension methods, which might end up modifying the request path as it has been done in this answer. See for example the implementation of RewriteRule
Specifically to the OP question, you would need to implement your own IRule class (either from scratch or extending an existing one like RewriteRule, which is based on a regex). You would possibly complement it with a new AddMyRule() extension method for RewriteOptions.
You can create your own middleware and add it to the request pipeline before the MVC routing.
This allows you to inject your code into the pipeline before the MVC routes are evaluated. This way you will be able to:
Inspecting the path in the incoming request
Search in the database for an eventId or hostId with the same value
If event or host were found, update the incoming request path to Event/Index/{eventId} or Home/Organization/{hostId}
Let the next middleware (MVC routing) take care of the request. They would see any changes to the request path made by the previous middleware
For example, create your own EventIdUrlRewritingMiddleware middleware that will try to match the incoming request path against an eventId in the database. If matched, it will change the original request path to Event/Index/{eventId}:
public class EventIdUrlRewritingMiddleware
{
private readonly RequestDelegate _next;
//Your constructor will have the dependencies needed for database access
public EventIdUrlRewritingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var path = context.Request.Path.ToUriComponent();
if (PathIsEventId(path))
{
//If is an eventId, change the request path to be "Event/Index/{path}" so it is handled by the event controller, index action
context.Request.Path = "/Event/Index" + path;
}
//Let the next middleware (MVC routing) handle the request
//In case the path was updated, the MVC routing will see the updated path
await _next.Invoke(context);
}
private bool PathIsEventId(string path)
{
//The real midleware will try to find an event in the database that matches the current path
//In this example I am just using some hardcoded string
if (path == "/someEventId")
{
return true;
}
return false;
}
}
Then create another class HostIdUrlRewritingMiddleware following the same approach.
Finally add your new middlewares to the pipeline in the Startup.Configure method, making sure they are added before the Routing and MVC middleware:
app.UseMiddleware<EventIdUrlRewritingMiddleware>();
app.UseMiddleware<HostIdUrlRewritingMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
With this configuration:
/ goes to the HomeController.Index action
/Home/About goes to the HomeController.About action
/Event/Index/1 goes to the EventController.Index action id=1
/someEventId goes to the EventController.Index action, id=someEventId
Please note there are no http redirects involved. When opening /someEventId in the browser there is a single http request and the browser will show /someEventId in the addess bar. (Even if internally the original path was updated)

MVC3 (Razor) - not changing the UI Culture language on programmatically

I am trying to develop a MVC3 (razor) application with Select language functionality.
Using the following view as a Partial view on _Layout.cshtml
_SelectCulture
<text>
#Html.ActionLink("English", "SetCulture", new { controller = "Culture", culture = "en-GB" })
|
#Html.ActionLink("Welsh", "SetCulture", new { controller = "Culture", culture = "cy-GB" })
</text>
<div>
#System.Threading.Thread.CurrentThread.CurrentUICulture.ToString()
</div>
CultureController
public ActionResult SetCulture(string culture)
{
System.Globalization.CultureInfo ci = new System.Globalization.CultureInfo(culture);
System.Threading.Thread.CurrentThread.CurrentCulture = ci;
System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
return RedirectToAction("Index", "Home");
}
But its Still not changing the Language.
Any help please.
Thanks
Well, you are changing the language of the current thread. The current thread ends with the current request which is a little bit later after your controller action executes. Then you are redirecting to some other controller action. Then ASP.NET spawns a new thread to serve this request which obviously doesn't have the culture set.
So you will have to persist this change somewhere. Basically there are 3 different approaches:
route variable
cookies
session
I am putting them in the order of preference. The first approach consists into integrating a {culture} token in all your routes. IMHO this is the best approach in terms of SEO as well. So you will redirect for example to /fr/home/index if you want to get your site in French. You could then use a custom action filter attribute which will run before each action, inspect the culture route parameter and set the current thread culture (this time for the current action).
Cookies and sessions also involve persisting the current language between the requests. In the first example this is done on the client whereas in the second it is done in the server. Once again a custom action filter could be used to read the value of the language before each action and reflect the current thread culture.
You may take a look at the following guide which uses Session to persist the current language.

Generating a client URL in a Timer running in global.asax

I have an MVC 2 app that has a System.Timers.Timer object starting up during Application_Start in global.asax. One of the things this timer needs to do is generate a daily e-mail to users when they have an item to review in their work queue. Inside the e-mail is a link back to the item to review.
In code I have running in various controller actions, I use code like this to generate URLs that can be e-mailed to users:
UriBuilder builder = new UriBuilder(Request.Url);
builder.Path = Url.RouteUrl("ReviewDetails", new { id = reviewId });
return builder.ToString();
I would like to do the same inside my Timer's elapsed method in global.asax, but there is no HttpContext at that point, so I'm not able to programatically determine the URL.
I think the only solution is to store the site's root (ex: http://intranet.domain.com/MyApp) in the web.config and then use that to build the URL, like this:
string url = string.Concat(ConfigurationManager.AppSettings["SiteRoot"], "/Review/", reviewId.ToString());
I'm putting this out in the cloud to see if there are any better solutions to programatically genererating a URL for the client when the HttpContext is not available.
You can capture the url in the Application_Start method and store it in the ApplicationState collection.
There is always an HttpContext. Try the HttpContext.Current static method.

Resources