1
votes

I'm trying to create a HtmlHelper that is based upon the way in which Html.RenderAction works. The way in which it will differ from RenderAction is that along with the "ActionName" and "ControllerName", it is to take a string that in turn will correspond to a value in Web.Config. This value in the config is that of a URL.

The reason for this is that although my Controller/Action works perfectly in it's own native Project, I need to get the results of this Controller/Action from it's sibling Projects. I intend to do this by constructing the required URL using a helper and the details currently held in Web.Config of each of the sibling Projects.

I have the following coded already:

    public static void RenderActionToSpecifiedAssembly(this HtmlHelper helper, string actionName, string controllerName, string parentAssembly)
    {
        var uriFromWebConfig = new Uri(ConfigurationManager.AppSettings[parentAssembly]);
            //uriFromWebConfig == "http://ProjectNumberOne.com/"
        var parentUri = new Uri(uriFromWebConfig);
        var path = controllerName + "/" + actionName;
        var redirect = new Uri(parentUri, path).AbsoluteUri;
            //var redirect == "http://ProjectNumberOne.com/MyController/MyAction"
        //******************
    }

What I'm struggling with now is what to put at the **********'s. What I want this helper to do is return the result of http://ProjectNumberOne.com/MyController/MyAction.

  • If I type this URL into my address bar, it returns the expected html page.
  • If I use Html.RenderAction("MyAction","MyController") in the parent Project, it returns the expected html page.

What I don't know how to do is specify at the end of the helper the return URL for the sibling Projects to get the resulting html from.

Any ideas?

3
Is your problem that ConfigurationManager.AppSettings[parentAssembly] returns empty instead of the value on web.config? How is this value assigned? I think you should set this variable like parentASsembly = Assembly.GetEntryAssembly(). Also, consider tagging this kind of question with ReflectionDavi Fiamenghi
ConfigurationManager.AppSettings[parentAssembly] returns as expected. I pass the string of "ProductOne" as the third parameter of the helper method. From there, the method itself looks in Web.Config to see what is held against the item called "ProductOne". (In this case it's a URL). My problem is when I'm in either Product2 or Product3 as I want it to get the results from http;//ProjectNumberOne.com/MyController/MyAction and not http;//ProjectNumberTwo.com/MyController/MyAction or http;//ProjectNumberThree.com/MyController/MyActionStu1986C
It's hard to tell exactly what you're going for but it seems to me that you're trying to fetch a configuration value from a different configuration per assembly. You might want to take a look at this.David Peden
I've changed title to what I think reflect your question - feel free to improve. You may want to remove most of the code from your question that is not directly related to actual problem (i.e. all config related code/text can be safely removed)Alexei Levenkov

3 Answers

0
votes

Some of the details in your post make it difficult to determine whether you want to accomplish your goal by using reflection or by using http (or maybe you do not care as long as you can get the result you want).

I do not recommend that you try to utilize reflection to accomplish this. (there are a lot of reasons for this but the important thing is that using http is going to be easier and more straight-forward)

Based on what you've posted the approach I would use is to create an HtmlHelper that renders an IFRAME tag with the desired URL as the src.

Something like this (lots of opportunity for defensive coding here BTW):

    public static MvcHtmlString RenderIFrameForCompanionSite(this HtmlHelper helper, string actionName, string controllerName, string baseUrlSettingKey)
    {
        var baseUrlFromWebConfig = ConfigurationManager.AppSettings[baseUrlSettingKey];
        var companionSiteUri = new Uri(baseUrlFromWebConfig);
        var path = controllerName + "/" + actionName;
        var redirect = new Uri(companionSiteUri, path).AbsoluteUri;

        return new MvcHtmlString("<iframe style='width: 100%' src='" + redirect + "'></iframe>");
    }

A view in the ProjectTwo site would reference an action in ProjectOne like this:

@Html.RenderIFrameForCompanionSite("MyAction", "MyController", "ProjectOne")

I'm assuming that your application(s) have a business need to be in distinct sites/projects. But if that is not mandatory then you might consider whether it would be possible to organize the related content differently (such as a single site with multiple areas rather than multiple sites) which would make it easier for you to share the content/behaviors between them.

0
votes

I found that although the IFRAME approach offered by David worked as required, using an IFRAME still unsettled me. This post made me realise that they're not so great: Good Reasons why not to use Iframes in page content

I found that the following implementation served it's purpose and gave me the required result:

public static IHtmlString RenderActionToSpecifiedAssembly(this HtmlHelper helper, string actionName, string controllerName, string parentAssembly)
{
        var parentWebConfigarentValue = new Uri(ConfigurationManager.AppSettings[parentAssembly]);
        var path = controllerName + "/" + actionName;
        var redirect = new Uri(parentWebConfigarentValue, path).AbsoluteUri;
        var request = (HttpWebRequest)WebRequest.Create(redirect);

        var result = (HttpWebResponse)request.GetResponse();

        String responseString;

        using (Stream stream = result.GetResponseStream())
        {
            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
            responseString = reader.ReadToEnd();
        }

        return new HtmlString(responseString);
}

Using this approach allows me to make a request to the Parent Controller/Action, write it to string then return the HTML to the page to be rendered. Works a charm! :)

0
votes

Your solution better explains what you want, but is very inflexible. What if you need to pass additional route values to the request? It would be better to mimic the UrlHelper class by providing overloads that you can use to build any URL you want.

using System;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

public static class HtmlHelperExtensions
{
    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, string parentAssembly)
    {
        return RenderActionToSpecifiedAssembly(
            helper, actionName, null, null, null, parentAssembly);
    }

    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, object routeValues, 
        string parentAssembly)
    {
        return RenderActionToSpecifiedAssembly(helper, actionName, 
            null, new RouteValueDictionary(routeValues), null, parentAssembly);
    }

    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, string controllerName, 
        string parentAssembly)
    {
        return RenderActionToSpecifiedAssembly(helper, actionName, 
            controllerName, null, null, parentAssembly);
    }

    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, RouteValueDictionary routeValues, 
        string parentAssembly)
    {
        return RenderActionToSpecifiedAssembly(helper, actionName, 
            null, routeValues, null, parentAssembly);
    }

    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, string controllerName, 
        object routeValues, string parentAssembly)
    {
        return RenderActionToSpecifiedAssembly(helper, actionName, 
            controllerName, new RouteValueDictionary(routeValues), 
            null, parentAssembly);
    }

    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, string controllerName, 
        RouteValueDictionary routeValues, string parentAssembly)
    {
        return RenderActionToSpecifiedAssembly(helper, actionName, 
            controllerName, routeValues, parentAssembly, null);
    }

    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, string controllerName, 
        object routeValues, string protocol, string parentAssembly)
    {
        return RenderActionToSpecifiedAssembly(helper, actionName, 
            controllerName, routeValues, protocol, parentAssembly, null);
    }

    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, string controllerName, 
        RouteValueDictionary routeValues, string parentAssembly, string port)
    {
        var hostName = ConfigurationManager.AppSettings[parentAssembly];
        var url = GenerateContentUrl(helper, actionName, 
            controllerName, routeValues, null, hostName, port);
        return RenderContents(url);
    }

    public static IHtmlString RenderActionToSpecifiedAssembly(
        this HtmlHelper helper, string actionName, string controllerName, 
        object routeValues, string protocol, string parentAssembly, string port)
    {
        var hostName = ConfigurationManager.AppSettings[parentAssembly];
        var url = GenerateContentUrl(helper, actionName, 
            controllerName, new RouteValueDictionary(routeValues), 
            protocol, hostName, port);
        return RenderContents(url);
    }

    private static string GenerateContentUrl(this HtmlHelper helper, 
        string actionName, string controllerName, RouteValueDictionary routeValues, 
        string protocol, string hostName, string port)
    {
        var currentUri = helper.ViewContext.RequestContext.HttpContext.Request.Url;

        // Ensure we have an absolute path
        if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(hostName))
        {
            // Match the scheme of the current request so we don't get a
            // security warning in the browser.
            protocol = currentUri.Scheme;
        }

        // Allow caller to override the port so it doesn't have 
        // to be the same as the current request.
        string currentUrl = currentUri.Scheme + Uri.SchemeDelimiter 
            + currentUri.DnsSafeHost;
        if (!string.IsNullOrEmpty(port))
        {
            currentUrl += ":" + port;
        }
        currentUrl += "/";
        var homePageUri = new Uri(new Uri(currentUrl, UriKind.Absolute), "/");

        // Create a TextWriter with null stream as a backing stream 
        // which doesn't consume resources
        using (var nullWriter = new StreamWriter(Stream.Null))
        {
            // Create a fake context at the home page to ensure that ambient values  
            // from the request are excluded from the generated URL.
            // See: https://aspnetwebstack.codeplex.com/workitem/1346
            var httpContext = CreateHttpContext(homePageUri, nullWriter);
            var requestContext = new RequestContext(httpContext, new RouteData());
            return UrlHelper.GenerateUrl(null, actionName, controllerName, 
                protocol, hostName, null, routeValues, helper.RouteCollection, 
                requestContext, true);
        }
    }

    private static HttpContextBase CreateHttpContext(Uri uri, TextWriter writer)
    {
        if (uri == null)
            throw new ArgumentNullException("uri");
        if (writer == null)
            throw new ArgumentNullException("writer");

        var request = new HttpRequest(string.Empty, uri.ToString(), uri.Query);
        var response = new HttpResponse(writer);
        var httpContext = new HttpContext(request, response);
        return new HttpContextWrapper(httpContext);
    }

    private static IHtmlString RenderContents(string url)
    {
        var request = (HttpWebRequest)WebRequest.Create(url);
        var result = (HttpWebResponse)request.GetResponse();

        String responseString;
        using (Stream stream = result.GetResponseStream())
        {
            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
            responseString = reader.ReadToEnd();
        }

        return new HtmlString(responseString);
    }
}

You may also wish to parse the port out of the configuration value so you can have both http and https URLs configured there and so they are tightly bound together with the host name, which could remove a few overloads. In that case, you should probably use the protocol of the current request to determine which (HTTP or HTTPS) configuration value to retrieve and remove protocol as a passed in value.