2

I have a custom routing system on my web application where i have a database with my routes (url, Controller, Action and some more info). Every request that is made to the server goes to the database, queries that route by the url and returns a set of that i need to keep accessible for the future rendering Filter, Controller and View, i keep this data at a global var at my Global.asax file:

public static class GlobalVars
{
    public static Redirect reqContext { get; set; }
    public static UnidadeHotSite HotSite { get; set; }
}

My problem is that right now this information is getting mixed through users, sometimes when i'm at my browser and i have 3 open tabs and i refresh them at "almost" the same time the last one will get the route data of the previous loaded one.

For example my HotSite var keeps some subsite information like name, url, ID, etc., the subsite url would be: abc.com/subsite. When i load the first tab i get the right data which is the subsite data, the second tab is off the subsite area, abc.com, and i get the same data as the last loaded tab.

Now, what could be the problem ? I already used the NoStore on the OutputCache and tried to disable the session but nothing seems to work.

This is my Route Handler:

[OutputCache( NoStore = true, Duration = 0 )]
public class RouteHandler : MvcRouteHandler
{
    private static string RedirectAction { get; set; }
    private static string UnidadeURL { get; set; }

    protected override IHttpHandler GetHttpHandler( RequestContext requestContext )
    {
        var friendlyUrl = (string)requestContext.RouteData.Values["RedirectUrl"];
        var objRet      = BuscaURL( friendlyUrl, requestContext );

        GlobalVars.reqContext = objRet[0] as Redirect;

        return base.GetHttpHandler(objRet[1] as RequestContext);
    }

    private static object[] BuscaURL( string pURL, RequestContext reqContext )
    {
        RedirectAction = "Index";
        var isHotSite  = BuscaHotSiteInfo( pURL );

        var tRedirect = !isHotSite ? BuscaURLWS( pURL ) : BuscaURLHS( pURL );

        if( tRedirect == null || "NotFound".Equals( tRedirect.controller ) )
        {
            Configuracoes.GeraLog( "pURL", pURL );
            if(tRedirect == null)
                Configuracoes.GeraLog( "tRedirect", "NULL" );
            else
                HelperController.GeraLog( tRedirect );

            tRedirect      = RedirectController.BuscaPaginaPorUrlWS( 5 );
            RedirectAction = "Index";

            reqContext.RouteData.DataTokens["Namespaces"] = "Site.Controllers";
        }

        if( tRedirect != null && tRedirect.paginaId > 0 && RedirectAction == "Index" )
        {
            using( var db = new SkillSite() )
            {
                var pagina = db.Pagina.First( x => x.ID == tRedirect.paginaId && x.ativo == 1 );
                RedirectAction = pagina.action;
            }
        }

        reqContext.RouteData.Values["controller"] = tRedirect.controller;
        reqContext.RouteData.Values["action"]     = RedirectAction;
        reqContext.RouteData.Values["id"]         = tRedirect.ID;

        return new object[] { tRedirect, reqContext };
    }

    private static Redirect BuscaURLHS( string pUrl )
    {
        Redirect redirect = null;

        pUrl = pUrl.Replace( UnidadeURL, "" ).Replace( "teste", "" ).TrimStart( '/' ).TrimEnd( '/' );

        if( !string.IsNullOrEmpty( pUrl ) && !string.IsNullOrWhiteSpace( pUrl ) )
        {
            var splitUrl = pUrl.Split( '/' ).ToList();

            if( splitUrl.Count > 1 )
            {
                if( "cursos".Equals( splitUrl[0] ) )
                {
                    if( splitUrl.Count == 2 )
                    {
                        redirect = RedirectController.SearchPageByUrlWS( 1, splitUrl[1] );
                    }
                    else if( splitUrl.Count == 3 )
                    {
                        redirect = RedirectController.SearchPageByUrlWS( 2, splitUrl[1], splitUrl[2] );
                    }
                }
            }
            else
            {
                redirect = RedirectController.SearchPageByUrlWS( 0, "", "", splitUrl[0] );
            }
        }
        else
        {
            redirect = RedirectController.SearchPageByUrlWS( 0, "", "", "home" );
        }

        return redirect;
    }

    private static Redirect BuscaURLWS( string pUrl )
    {
        Redirect redirect = null;

        if( !string.IsNullOrEmpty( pUrl ) && !string.IsNullOrWhiteSpace( pUrl ) )
        {
            var splitUrl = pUrl.TrimEnd( '/' ).Split( '/' ).ToList();

            if( splitUrl.Count > 1 )
            {
                if( "cursos".Equals( splitUrl[0] ) )
                {
                    if( splitUrl.Count == 2 )
                    {
                        redirect = RedirectController.SearchPageByUrlHS( 1, splitUrl[1] );
                    }
                    else if( splitUrl.Count == 3 )
                    {
                        redirect = RedirectController.SearchPageByUrlHS( 2, splitUrl[1], splitUrl[2] );
                    }
                }
            }
            else
            {
                redirect = RedirectController.SearchPageByUrlHS( 0, "", "", splitUrl[0] );
            }
        }
        else
        {
            redirect = RedirectController.SearchPageByUrlHS( 0, "", "", "home" );
        }

        return redirect;
    }
}

This is the Controller that makes the searchs on the DB

[OutputCache( NoStore = true, Duration = 0 )]
public class RedirectController
{
    public static Redirect SearchPageByUrlWS( int tipo, string cursoCURL = "", string cursoURL = "", string redirectURL = "", string redirectURLTwo = "" )
    {
        using( var db = new Site() )
        {
            IQueryable<Redirect> redirects;

            if( tipo == 1 )
            {
                redirects = from redirect in db.Redirect
                            where redirect.url == cursoCURL && redirect.cursoCatId > 0
                            select redirect;
            }
            else
            {
                redirects = from redirect in db.Redirect
                            where redirect.url == redirectURL &&
                            redirect.cursoCatId == 0 &&
                            redirect.regulamentoId == 0 &&
                            redirect.noticiaId == 0 &&
                            redirect.ebookId == 0 &&
                            redirect.conhecaId == 0
                            select redirect;
            }

            return (redirects.ToList().Count > 0) ? redirects.ToList()[0] : null;
        }
    }

    public static HS_Redirect SearchPageByUrlHS( int tipo, string cursoCURL = "", string cursoURL = "", string redirectURL = "", string redirectURLTwo = "" )
    {
        using( var dbHS = new HS() )
        {
            IQueryable<HS_Redirect> redirects;

            if( tipo == 4 )
            {
                redirects = from redirect in dbHS.HS_Redirect
                            where redirect.url == redirectURL && redirect.noticiaId > 0 && redirect.unidadeCE == GlobalVars.HotSite.unidadeHS.unidadeCE
                            select redirect;
            }
            else
            {
                redirects = from redirect in dbHS.HS_Redirect
                            where
                                redirect.url           == redirectURL &&
                                redirect.cursoCatId    == 0 &&
                                redirect.regulamentoId == 0 &&
                                redirect.noticiaId     == 0 &&
                                redirect.ebookId       == 0 &&
                                redirect.conhecaId     == 0
                            select redirect;
            }

            return ( redirects.ToList().Count > 0 ) ? redirects.ToList()[0] : null;
        }
    }
}

EDIT:

I managed to make @NightOwl888 answer work with areas and everything else i needed, i'm not going to post this here since it's a little big so here is the code: http://pastebin.com/yTdWKMp4

EDIT 2

I have updated the file on pastebin with some changes to improve speed and usability: http://pastebin.com/yTdWKMp4

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Ariel
  • 911
  • 1
  • 15
  • 40

2 Answers2

2

You're using static Properties...generally not a good ideas unless you can make certain guarantees -- in this case, there is a guarantee this data will be shared across all users of your Application Domain.

Basically, use Session storage to keep track of an individual user's settings (see this article for more info).

Also, just as an aside, I noticed you've duplicated code in BuscaURLWS and BuscaURLHS -- don't do that! See the DRY for why not. Also, you're doing your URL tokenization manually; there are plenty of tools for doing that easily (see Uri for more information).

Community
  • 1
  • 1
funbrigade
  • 321
  • 2
  • 7
  • One last thing -- if I misunderstand you, and you're trying to do caching of some kind, using a static cache is totally fine. If you're trying to do this per-user, this answer is a good way of doing it. If you're trying to do something that should be done at *application startup*, then use `Application_Start` – funbrigade Oct 01 '15 at 20:58
  • Yeah, they look repeated because i hid part of the code, it was pretty extensive but thanks for the tip anyway. About the caching thing, i don't want any cache at all, i can't do it because the content can and will change, what i want is that each request is treated uniquely and they don't mess with each other – Ariel Oct 01 '15 at 21:04
  • Where could i user this since i need the session initialized to use it and i don't have it at this moment ? And i'm doing it at Application_Start, i have a custom route that will execute my RouteHandler – Ariel Oct 01 '15 at 21:12
  • 1
    Your `Session` object is automagically initialized for you -- you can do stuff like Session["key"] = "value" to store your user session-specific data. From inside of your RouteHandler you can use requestContext.HttpContext.Session to get to it. Let me know if that helps! – funbrigade Oct 01 '15 at 22:28
2

There are several problems with your approach:

  1. Never extend MvcRouteHandler to make a custom URL scheme. URL routing is a 2-way process and route handlers can only handle the incoming routes but do not build the outgoing URLs.
  2. You are not actually "routing" here. Routing implies that you are mapping the incoming request to a resource. What you are doing is allowing the request to come in, deciding what to do with it, then redirecting the browser to another URL. This causes another unnecessary round trip to the server, which is bad for performance and SEO.
  3. The OutputCache attribute only works on controller action methods, and it only applies to caching content of the views. It does not apply to caching data.

If you want to have database driven routing, you should subclass RouteBase. This gives you the opportunity to map the URL to a set of route values (which represents a controller action and parameters) and also map route values back to a URL (so ActionLink and RouteLink will work correctly in your views).

Have a look at this answer for a reliable approach. It also includes a cache for the URL data and thread locking to ensure the cache is only updated by a single thread (and database only called once) when the cache expires.

If you need to make it more reusable (that is, work with more controller and action methods), you could make it more generic similar to this MVC 6 sample by passing the controller and action information and a data provider instance through the constructor of your custom RouteBase and registering the route more than one time in your configuration (with different parameters, of course).

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Yeah, it looks like it's going to fix my problem but how would i implement that MVC6 example on this code you provided ? Thanks :D – Ariel Oct 02 '15 at 13:08
  • 1
    Use the other solution in [this answer](http://stackoverflow.com/questions/31934144/multiple-levels-in-mvc-custom-routing/31958586#31958586). The MVC 6 solution was just so you can see how to make it more generic by exposing the parameters onto the constructor of the class and create a separate service for the data provider. Other than that, they are basically the same. – NightOwl888 Oct 04 '15 at 02:26
  • It worked greatly, just one question...what does GetVirtualPath ? I changed a little your code so it would do what i needed, can you take a look ? Thanks. http://pastebin.com/CZYPSH1J – Ariel Oct 05 '15 at 11:58
  • i'm having some trouble with what i think is related to this code, in some pages i'm having 302 redirects and i get a too many redirects response, what could be causing this problem ? – Ariel Nov 25 '15 at 18:29
  • 1
    Areas - you need to implement [`IRouteWithArea`](https://msdn.microsoft.com/en-us/library/system.web.mvc.iroutewitharea(v=vs.118).aspx) to support them. – NightOwl888 Nov 28 '15 at 16:26
  • 1
    Too many redirects indicates you have cyclic redirect scenario. Typically, this doesn't happen just by using routing. It happens when you direct the request somewhere that continually does a redirect back to itself (or to an error page that can't fire because it hits a redirect first, which refires the error page again). – NightOwl888 Nov 28 '15 at 16:29
  • Thanks again buddy :D – Ariel Nov 30 '15 at 10:45