MVC UrlHelper called from Service
Asked Answered
A

1

1

I have a static class that sends emails with links to certain pages of my site. That link is getting dynamically generated with this code:

UrlHelper urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
string url = urlHelper.Action("Details", "Product", new { id = ticketId }, "http");

The problem is I now also have a service that periodically compares the creation date with the current date and sends those mails automatically. The code crashes of course and says HttpContext.Current is null (because it ain't a request).

I tried some things like that:

private static System.Web.Routing.RequestContext requestContext;
private static System.Web.Routing.RequestContext RequestContext {
    get
    {
        if(requestContext == null)
            requestContext = HttpContext.Current.Request.RequestContext;
        return requestContext;
    }
}

But when I need RequestContext the second time for the UrlHelper.Action crashes saying Null Reference Exception.

I failed to somehow save/remember/pass the UrlHelper or the HttpContext to have access when calling the mail method over my service.

Arsenic answered 9/8, 2013 at 16:3 Comment(8)
If you're in a controller, you should be using the ControllerContext. Where does this code live? If this is in a library, then you should pass in the appropriate context to the method that creates the URL Helper. However, I can't see a web specific thing like a URL being in the service layer -- it should be in the web layer of your project, since it's a webby-thing.Kazim
I have a static class called MailMethods that does all the sending stuff with the UrlHelper. The real problem now is that I have a seperate service that is in the same solution file and can access all the public methods but of course has no HttpContext.Current...Arsenic
@yourmother then you have a design problem. If your helper depends on HttpContext.Current then it should be only used in an environment where it's available - or alternatively you should refactor out the need for HttpContext.Current.Incorrigible
I agree with @James. You'll need to have the controller pass in the HttpContext into the service.Woodman
Yeah maybe, but how could the service/app know the url of the website (dynamically)? How could I pass it to the service?Arsenic
@yourmother The thing in your web app that calls this 'static' method should pass in the context to that method as a parameter. Not sure why it's a static method (I don't believe it should be).Kazim
I added a sample what I've tried and what doesn't work. The RequestContext seems to be accepted just once!? I don't see what making it non-static or passing it would help with my problem with the service (if I am wrong explain in more details).Arsenic
I would pass to email service full email body as text (already resolved URL's), or just list of resolved URLs. Store those values somewhere as template and just resend those next time. Those URL's are not changing dynamically, so no need to calculate them each time before sending email.Antiserum
A
1

Thanks for all your help. Predefining the URL was no option in my case. I found more or less a solution for my problem. I know it might not be the most beautiful code but it works quite well and noone seems to have a better one, so no downvotes please.

In global.asax.cs I added this class:

class FirstRequestInitialisation
{
    private static string host = null;

    private static Object s_lock = new Object();

    // Initialise only on the first request
    public static void Initialise(HttpContext context)
    {
        if (string.IsNullOrEmpty(host))
        { //host isn't set so this is first request
            lock (s_lock)
            { //no race condition
                if (string.IsNullOrEmpty(host))
                {
                    Uri uri = HttpContext.Current.Request.Url;
                    host = uri.Scheme + Uri.SchemeDelimiter + uri.Host + ":" + uri.Port;

                    //open EscalationThread class constructor that starts anonymous thread that keeps running. 
                    //Constructor saves host into a property to remember and use it.
                    EscalationThread et = new EscalationThread(host);
                }
            }
        }
    }
}

And I added this:

void Application_BeginRequest(Object source, EventArgs e)
{
    FirstRequestInitialisation.Initialise(((HttpApplication)source).Context);
}

Explaination what happens: On each and every request the FirstRequestInitialisation class gets called with the method Initialise with the context as parameter. This is never a problem because the context is known in a Application_BeginRequest (not like in Application_Start). The Initialise Method takes care that still the Thread is just called once and has a lock so that it will never crash. I eliminated my Service because I can't really communicate with it and instead I decided to make a Thread out of it. In this Initialise Method I call a class constructor EscalationThread with the host as parameter. In this constructor I create and start the thread that just keeps running.

I still don't have HttpContext and can't use the UrlHelper BUT I have the host and can do stuff like: string urlInMail = this.host + string.Format("/{0}/{1}/{2}", "Product", "Details", product.Id);

Arsenic answered 3/9, 2013 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.