3

I've create a SitemapResult class that derives from ActionResult. It allows the caller to add any number of URL resources, and it then outputs sitemap data in XML format.

public class SitemapResult : ActionResult
{
    private List<SitemapUrl> SitemapItems;

    public SitemapResult()
    {
        SitemapItems = new List<SitemapUrl>();
    }

    public void AddUrl(string url, DateTime? lastModified = null, SitemapFrequency? frequency = null, double? priority = null)
    {
        AddUrl(new SitemapUrl(url, lastModified, frequency, priority));
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.ContentType = "text/xml; charset=utf-8";

        using (XmlWriter writer = XmlWriter.Create(context.HttpContext.Response.Output))
        {

            // TODO: Write sitemap data to output

        }
    }
}

The problem is that the class stores all the URLs until ExecuteResult() is called. It seems like it would be more efficient if I could write each URL to the response as they are added rather than hold them all in memory and then write every thing at once.

Does anyone know of any good examples of overriding ActionResult to write data to the response as it becomes available? In this case, I would think ExecuteResult() won't need to write anything at all.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • Well unless you are passing the response when creating the result then it only has access to it via teh context in the ExecuteResult method – Nkosi Nov 27 '18 at 19:50
  • @Nkosi: I've added the basic layout but I'm not sure exactly what you wanted to see, since you didn't say. – Jonathan Wood Nov 27 '18 at 19:54
  • 1
    You could store a `Func>` and call that in `ExecuteResult`. The calls to `AddUrl` would then be replaced with `yield return`s. (A simple `IEnumerable` could also do, actually, the `Func` isn't necessary if there's not a lot of setup to get the enumeration going.) – Jeroen Mostert Nov 27 '18 at 19:59
  • Ok looking at the design of the class and the desired behavior, I now must ask if you have access to the Response in the controller and can add data as they arrive then I see no real reason to keep the custom result. – Nkosi Nov 27 '18 at 20:00
  • 1
    The `SitemapItems` could be passed as a constructor arg and used to populate result as well. – Nkosi Nov 27 '18 at 20:01
  • 1
    I am wondering if this might be premature optimization – Nkosi Nov 27 '18 at 20:01
  • @Nkosi: I don't get your first suggestion. It seems like it still wouldn't be called until `ExecuteResult`. I do have access to `HttpContext.Response` in the controller, but I was hoping to see a good example of that. Surely, I'm not the first to do this. As far as passing the items to the constructor, that doesn't help. They still need to be cached in memory. – Jonathan Wood Nov 27 '18 at 20:03
  • @JonathanWood I meant as you traverse what ever it is that fetches the urls, you add them to the response as you get them – Nkosi Nov 27 '18 at 20:05
  • @Nkosi: I don't see any problem with trying to make code efficient. I'm writing a generalized class and there's no telling how many items it might need to store. What on Earth is wrong with no caching everything in memory if it's not necessary? Why do some people get uptight if I think about making the code efficient. I'll never get that. – Jonathan Wood Nov 27 '18 at 20:06
  • @JonathanWood my intention was not to offend. Just throwing out ideas in an attempt to help you solve a problem. – Nkosi Nov 27 '18 at 20:07
  • @Nkosi: Yes, I'm want to write them to the response as I get them. But I don't want to do it directly in the controller as I'm writing a reusable class. Seems like I could still use a class the derives from `ActionResult` and that would make it simpler. But I'm open to not doing that if I see a good reason not to. – Jonathan Wood Nov 27 '18 at 20:08
  • @Nkosi: Sorry, I've seen the *premature optimization* claim before. In some instances it really rubs me the wrong way. Nothing personal. – Jonathan Wood Nov 27 '18 at 20:09
  • Ok Hows this. You create the result, that takes the Response as a dependency so you have access to it before ExecuteResult. As sites are added you pass to stream. ExecuteResult then become a non action as far as added content is concerned. You can still set headers and other non content related functionality – Nkosi Nov 27 '18 at 20:10
  • @Nkosi: Right, that's the basic approach I had in mind. I just was hoping to see some examples of this, but wasn't able to find any. – Jonathan Wood Nov 27 '18 at 20:12
  • Do you want me to add an example or you already have that covered since you mentioned you were already had that approach in mind – Nkosi Nov 27 '18 at 20:15
  • @Nkosi: No, I understand the basics. I'm just thought if someone's done it, they might have thought of something I hadn't considered. I'll just write it if I can find an example. But, again, I can't imagine I'm the first person to consider this. Thanks. – Jonathan Wood Nov 27 '18 at 20:17
  • @JonathanWood no problem. Glad to help. – Nkosi Nov 27 '18 at 20:17

1 Answers1

1

What you are trying to achieve, is building your model inside the view (custom view)... that's not a good practice... in MVC, controllers are responsible for building the model and passing it to the view... views are responsible for displaying the model and should have as little logic as possible.


It seems like it would be more efficient if I could write each URL to the response as they are added rather than hold them all in memory and then write every thing at once.

Why? You need to keep SitemapItems somewhere in the memory, so even if you write them to response, they are still kept in memory until you return the response... and I think it would be more efficient to serialize the whole list to XML at one go, as opposed to serializing each SitemapUrl individually.


There is a very elegant solution to your question on this pluralsight course:

public class SitemapResult : ActionResult
{
    private object _data;

    public SitemapResult(object data)
    {
       _data = data;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        // you can use reflection to determine object type
        XmlSerializer serializer = new XmlSerializer(_data.GetType());
        var response = context.HttpContext.Response;
        response.ContentType = "text/xml";
        serializer.Serialize(response.Output, _data);
    }
}

And you build your model in the controller, and pass it to the view:

return new SitemapResult(SitemapItems);

If you want to directly write to the Response you can do it in the controller:

public MyController : controller
{
    public void GetSiteMapUrls()
    {
        XmlSerializer serializer = new XmlSerializer(SitemapItems.GetType());
        Response.ContentType = "text/xml";
        serializer.Serialize(Response.Output, SitemapItems);
    }
}
Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137
  • I'm getting the data from various sources, including the database. It is not necessary to store the entire list in memory except to cache the results before sending them to the response. It clearly seems more efficient not to cache that data. Moreover, it seems like it would be much faster if there was a way to send the data as I'm collecting it rather than caching it first. The code you posted is very similar to the code I posted. Mine just helps collect the data. I can experiment with using XmlSerializer of XmlWriter, but as I've described, this doesn't seem the most efficient approach. – Jonathan Wood Nov 28 '18 at 09:59
  • I am not sure if I understood... you have a list of urls, right? You don't want to store the entire list in memory - but isn't the entire list going to be added to response in serialized xml? – Hooman Bahreini Nov 28 '18 at 10:10
  • I construct a list of URLs. But that extra step seems unnecessary to me. I'd rather just send each URL to the response as I find it rather than storing it in a separate collection in memory. – Jonathan Wood Nov 28 '18 at 10:13
  • If you don't want the list of URLs, you can directly write it to the response, in controller (see updated answer). If I have understood you correctly, what you want is to build your Serialized XML list in the SiteMapResult... that is not how MVC is designed. Model is supposed to be built in controller and passed to the view. – Hooman Bahreini Nov 28 '18 at 10:30