12 października 2010, 10:16 dario Komentarze (0)

Wykres MSChart, ASP.NET MVC i cache

Od dłuższego czasu Microsoft udostępnia kontrolkę do generowania różnego rodzaju wykresów. Możliwości kontrolki są naprawdę bardzo duże. Dzięki niej można wygenerować naprawdę ładne wykresy.

Wykres

Jak wiadomo w aplikacji MVC kontrolka ta nie zadziała w taki sam sposób jak w aplikacji WebFormsowej. Tu nie można zwyczajnie położyć kontrolki na formie, gdyż wymaga ona PostBack'a. Musimy stworzyć obiekt samemu i go skonfigurować w kodzie. Na szczęście MSCharts ma zaimplementowane szablony, dzięki czemu w łatwy sposób (bez rekompilacji) można zmieniać wygląd wykresów.

var chart = new Chart();
//...
string filename = "~/App_Code/charts/mychart.png".ToPhysical();
chart.SaveImage(fileName, ChartImageFormat.Png);

Jak widać stworzenie wykresu jest proste. Trzy kropki należy wypełnić kodem, który skonfiguruje nasz wykres i wypełni danymi. Przykłady publikuje sam Microsoft.

Cache

No dobra, ale jak odwołać się do tego wykresu i gdzie ten cache? Już odpowiadam. Aby wykres zaistniał na stronie musimy się po niego odwołać. W tym celu tworzymy akcję kontrolera, która nam go zwróci.

public ActionResult Graph()
{
    if (UserContext.LoggedUser == null)
    {
        return PageNotFound();
    }

    string filename = ChartHelper.GetFileNameFor(UserContext.LoggedUser);
    if (!System.IO.File.Exists(filename))
    {
        // Nie było wczesniej, więc generujemy wykres
    }
    return new FilePathResultWith304(filename, "image/png");
}

Wywołanie z widoku wygląda następująco.

    <%=Html.Image(Url.Action("graph"), "Wykres") %>

Jak pewnie zauważyliście akcja zwraca FilePathResultWith304. Jest to klasa, która wysyła kontent wraz z nagłówkiem "If-Modified-Since". Dzięki temu kolejne odwołanie sprawdza ten nagłówek i porównuje z datą ostatniej modyfikacji pliku. Jeśli wykres nie jest nowszy to do przeglądarki wysyłany jest kod 304 Not modified i zero bajtów :)

public class FilePathResultWith304 : ActionResult
{
    private const string ifModifiedSince = "If-Modified-Since";

    public FilePathResultWith304(string fileName, string contentType)
    {
        FileName = fileName;
        ContentType = contentType;
    }

    public string FileName { get; private set; }
    public string ContentType { get; private set; }

    public override void ExecuteResult(ControllerContext context)
    {
        var response = context.HttpContext.Response;
        response.Clear();
        response.BufferOutput = false;
        response.ContentType = ContentType;

        if (!File.Exists(FileName))
        {
            response.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
            return;
        }

        var fi = new System.IO.FileInfo(FileName);
        var lastWriteTime = fi.LastWriteTimeUtc.Date
            .AddHours(fi.LastWriteTimeUtc.Hour)
            .AddMinutes(fi.LastWriteTimeUtc.Minute)
            .AddSeconds(fi.LastWriteTimeUtc.Second);
        var request = context.HttpContext.Request;

        var smodifiedSince = request.Headers[ifModifiedSince];
        if (!smodifiedSince.IsNullOrEmpty())
        {
            var modSince = smodifiedSince.Split(';');
            DateTime modifiedSince;
            if (modSince.Length > 0 && DateTime.TryParse(
                modSince[0], null,
                System.Globalization.DateTimeStyles.AdjustToUniversal,
                out modifiedSince))
            {
                if (modifiedSince.CompareTo(lastWriteTime) >= 0)
                {
                    response.Cache.SetCacheability(System.Web.HttpCacheability.Public);
                    response.Cache.SetLastModified(lastWriteTime);
                    response.Cache.SetMaxAge(TimeSpan.Zero);
                    response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
                    response.StatusDescription = "Not Modified";
                    return;
                }
            }
        }
        response.Cache.SetCacheability(System.Web.HttpCacheability.Public);
        response.Cache.SetLastModified(lastWriteTime);
        response.Cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
        response.Cache.SetMaxAge(TimeSpan.Zero);
        response.TransmitFile(FileName);
    }
}

Zabieg ten daje nam jeszcze jedną zaletę. Załóżmy, że następuje stosowna zmiana danych aplikacji, która powinna wygenerować nowy wykres. Na pierwszy rzut oka sposób postępowania mógłby być taki, aby zmienić dane w transakcji i zaraz potem wygenerować nowy wykres. Niestety wydłuży to czas takiej operacji, ale możemy odwlec moment wygenerowania wykresu dopóki ktoś po niego nie poprosi. W związku z tym po zmianie danych wystarczy na koniec usunąć plik wykresu z dysku. Akcja Graph wykona sprawdzenie dostępności pliku i w przypadku braku wygeneruje nowy.

Podsumowując

Jak widać tworzenie wykresów wcale nie jest trudne, a "ludzie biznesu bardzo lubią" takie kolorowe wykresiki, które wzbogacają aplikację. Kolejnym razem będzie o tym jak dodać aktywną mapę do danych wykresu wygenerowanego w powyższy sposób. :)

Tagi:

Asp.net-mvc

Pingbacks and trackbacks (1)+

Komentarze zablokowane

O autorze

Dariusz Gil - projektant i programista aplikacji internetowych budowanych na platformie Microsoft w technologii ASP.NET (C#) oraz MS SQL Server. Obecnie właściciel (narazie :)) jednoosobowej firmy Softio.

Filtruj używając APML