ASP.NET MVC Razor Concatenation

Można wygooglować, ale ja nie od razu na to wpadłem (może nie wiedziałem jak nazwać), więc chciałbym się podzielić i zaoszczędzić czasu komuś.

ASP.NET MVC Razor Concatenation

Jest to lepsze od string.Format():

ASP.NET MVC Razor string.Format

StackOverflow: ASP.NET MVC Razor Concatenation

Update, inny przykład

<li class="@(Model == 0 ? "checked" : "")" >

All the small things (thoughts about constraints in EpiServer development)

The small things, the ones that take really not much time when implemented right at first opportunity (not delaying until … never). And how they will save a lot of project time down the road, for you developer and for other team members.

List of things I would like to have on every page/block/property

  • Make not shared blocks not visible in edit mode. AvailableInEditMode = false.
  • Add LimitPageType for every PageReference property.
  • AvailablePageTypes for every page. I like shaping the structure (and thus making it obvious to everybody) from the whole beginning.
  • Validate what can be dragged on ContentArea – not always good. You have to be sensitive here, as sometimes Editors will be confused and disapointed that they cannot save and forget what they did at this editing session.

Episerver 7 saving page error

Be explicit to everybody

Does it sound like self describing code to you? It sounds like this at least for me. Imagine that time you saved not doing it, have to be now spend on writing documentation/manual or comments in Jira. Or you are distracted by you colleagues questions, because they actually started using (testing is more appropriate here) the application. They of course report some things as bugs to you. And later exact the same things come like boomerang from the customer Editors.

I bet everybody wants to avoid this kind of additional work.

Don’t do it later

Some people have approach to do it near the end of development, when all more important core features are done and we finally ‚have time’. I have been doing software for years and this sweat moment almost never happens 🙂

Sum up about context changing

What I want to remember from this post is that context changing is very costly and its quite cheap to implement this constraints right when you are working at particular feature.

Do you have some other important things to add or consider some mentioned here concept not worth bothering?

Custom ContentArea without wrapper tags

The default ContentArea rendering will generate something similar to:

<div>
    <div>yourBlockMarkup1</div>
    <div>yourBlockMarkup2</div>
    ...
</div>

This is completly fine in most cases, but sometimes you have markup that you need total control of rendering.

No wrapping tags

I had such a situation. I used it for a slider, where you can put Blocks of different types (representing different type of slides).

/// <summary>
/// Renders blocks without wrapping (div) tags.
/// </summary>
public class NoWrappersContentAreaRenderer
{
    private readonly IContentRenderer _contentRenderer;
    private readonly TemplateResolver _templateResolver;
    private readonly ContentRequestContext _contentRequestContext;

    public NoWrappersContentAreaRenderer(IContentRenderer contentRenderer, 
        TemplateResolver templateResolver,
        ContentRequestContext contentRequestContext)
    {
        _contentRenderer = contentRenderer;
        _templateResolver = templateResolver;
        _contentRequestContext = contentRequestContext;
    }

    public virtual void Render(HtmlHelper helper, ContentArea contentArea)
    {
        var contents = (_contentRequestContext.IsInEditMode(helper.ViewContext.HttpContext)
                            ? contentArea.Contents
                            : contentArea.FilteredContents).ToArray();

        foreach (var content in contents)
        {
            var templateModel = _templateResolver.Resolve(helper.ViewContext.HttpContext, 
                content.GetOriginalType(), content,
                TemplateTypeCategories.MvcPartial, null);

            using (new ContentAreaContext(helper.ViewContext.RequestContext, content.ContentLink))
            {
                helper.RenderContentData(content, true, templateModel, _contentRenderer);
            }
        }
    }
}

Above code generates only the blocks without wrappings tags:

yourBlockMarkup1
yourBlockMarkup2
...

Don’t forget to register view for your Block. Does not require any extra BlockController.

// \Business\Rendering\TemplateCoordinator.cs

viewTemplateModelRegistrator.Add(typeof(RoyalSliderItemBlock), new TemplateModel
{
    Name = "RoyalSliderItemBlock",
    AvailableWithoutTag = true,
    Path = BlockPath("RoyalSliderItemBlock.cshtml")
});

Define a tag

public static class Tags
{
    public const string NoWrappersContentArea = "NoWrappersContentArea";
}

Based on this tag choose a rendering method
/Views/Shared/DisplayTemplates/ContentArea.cshtml:

@model EPiServer.Core.ContentArea

@if (Model != null)
{
    var tag = Html.ViewContext.ViewData["tag"] as string;
    switch (tag)
    {
        case Tags.NoWrappersContentArea:
            Html.RenderNoWrappersContentArea(Model);
            break;
        default:
            Html.RenderContentArea(Model);
            break;
    }
}

Which is defined here
/Extensions/HtmlHelperExtensions.cs:

public static void RenderNoWrappersContentArea(this HtmlHelper helper, ContentArea contentArea)
{
    var renderer = ServiceLocator.Current.GetInstance<NoWrappersContentAreaRenderer>();
    renderer.Render(helper, contentArea);
}

Outputting ContentArea using defined tag

<div id="huge-slider" class="royalSlider rsHuge" style="height: 540px;">
    @Html.PropertyFor(m => m.CurrentPage.RoyalSliderItems, new
        {
            Tag = Tags.NoWrappersContentArea,
        })
</div>

Cumbersome, but works

I was once trying to avoid this 3 steps rendering (taken from Alloy Templates) but I failed and have to revert everything. It was not so obvious at the first look that ContentArea can be very often null and this check in ContentArea.cshtml is very important. (I don’t remember exactly but I will not try again.)

Side note

When I first created this custom renderer I named it after the ContentArea that was making use of it and it was TechnicalDetailsContentAreaRenderer. Later I realized that this particular use case is not so important and the behaviour of this class is more general so I coined a name NoWrappersContentAreaRenderer.

Update:

Source code clarification

All this stuff is based on Alloy Templates (missing class can be found there).

Resharper templates for Block and PageData

I always feel wrong when I need to do the most basic thing in Episerver development – adding a new page type. First options was to use „Add New Item…” dialog and make use of EPiServer CMS Visual Studio Extension.

add new episerver item

After that step I have to delete almost everything cause I stick to simplicity.

Second way is to copy-paste existing page. After that you have to rename, remove unwanted code and update Guid. Updating Guid is tricky cause you can forget and waste some time discovering it after rebuilding and running application.

R# file templates

What I do now, is using Resharper feature File Templates.

You go to Resharper->Templates Explorer, the click on File Templates:
resharper file templates

I created three: Block, PageData and ‚PageData more’ (all have auto generated new Guid). You can import my examples downloaded from here. Drag importet templates on top of the quicklist I also suggest delete all the templates under evil classic „ASP.NET (C#)”, and remove „Razor MVC Partial View” from quicklist (so PageData would be accessible from letter „P”).

Having this done, when you click Alt+Insert in Solution Explorer you will get simpler context menu with Block – „B” and PageData – „P”.

Resharper context menu add block

Your own template

Your can double click existing template or click “Create new”.

resharper create or edit file templates

The variables have to be set in the right pane. I just set them to the defaults like for “Class” template. The most important variable is $CLASS$, which will appear couple time in more sofisticated cases, the rest is mostly static.

resharper edit file template

I encourage you to change my defaults, probably all your Blocks should derive from SiteBlockData and the same for Pages (at least I do that in my projects). Maybe you have some favourite properties, you want always to have by default.

Bonus

My colleague Bartek is testing a lot so every factory need interface and contracts for interface methods. I added „Interface with contract” (Code Contracts).

Happy shortcuts using!

DotNETomaniaki to lenie

Takie stwierdzenie nasunęło mi się przedwczoraj podczas przeglądania dotNETomaiank.pl. Chodzi mi dokładnie o to, że nikt nie podbija. Zaglądnąłem na główną, a tam z góry na dół same jedynki i jedna dwójka (na 20 wpisów). Po co się starać fajny post napisać, jak tak naprawdę i tak wszystko jest płaskie i każdy kliknie. Od tego jest .NET Blogs PL, gdzie po prostu serwuje się aktualne posty z blogów, które chcą się tam pokazać.
Skoro w dotnetomaniaku ktoś się stara i udostępnia opcję wyróżnienia czegoś dobrego to korzystajmy.

Zbulwersował mnie zwłaszcza brak zainteresowania postem, który uważam za super pomocny, komuś tam pewnie pomógł/pomoże kiedyś: Debugowanie kodu zewnętrznych bibliotek, część II. NIE WIERZĘ, że nikt nigdy nie miał takiej potrzeby. Ile to razy słyszałem „no niestety tak nie można zrobić, o ile prościej by było…”.
Spróbowałem, działa. Wiele nierozwiązywalnych problemów, od teraz będzie do ruszenia. Tylko niestety post zginie w gąszczu innych jedynek.

Porównanie w liczbach

Gdy jakiś mój post znajdzie się w serwisie teraz mam porównywalną ilość wejść cjak gdyby to było rok temu. Skąd więc tak drastyczny spadek liczby lajkujących?

  • Strona 1: 1 słownie jedna, (przed moimi podbiciami), nie-jedynka/strona
  • Strona 2: 7 nie-jedynek/strona
  • Strona 38: 7 jedynek/strona
  • Strona 39: 10 jedynek/strona
  • Strona 40: 7 jedynek/strona

  • Legenda: 20 postów mieści się na stronie. Strona nr 39 to było 484 dni temu.

    Porównanie

    Dziś: Kiedyś:

    dotNETomaniak dziś

    dotNETomaniak kiedyś

    Żeby coś zobaczyć to tylko w nowej karcie i bardzo powiększyć.

    A może tak ma być?

    Nie zrozumcie mnie źle, nie ma nic przeciwko wrzucaniu postów, które mają jedynki (tego się nigdy nie przewidzi). Chciałbym, żeby ktoś wchodzący rzadko i nie mający czasu na czytanie wszystkiego, mógł się zasugerować naszymi podpowiedziami i nie minęło go nic ważnego.

    Nie wiem po co się czepiam, może tak ma być. Może sezon ogórkowy (ale wakacje dopiero co się skończyły). Mam też lack social media skills i pewnie czegoś nie rozumiem – ale to akurat nic, czegoś się dowiem, będzie nauczka.

    Ludzie wchodzący tu z dotnetomaniaka – do roboty!

    Kudos

    Dzięki autorom i ludziom utrzymującym wymienione wyżej serwisy.

Routing w JavaScript – Sammy.js

Nie sądziłem, że to jest aż tak proste – do dzieła.

sammy-logo-header-large

Routing będzie manipulował naszym adresem (pasek adresu będzie te zmiany odzwierciedlał), będzie zmieniał naszą historię przeglądania. Dzięki temu przecisk „back” będzie działał bez przeładowywania strony. Zmiana adres będzie tylko po stronie clienta, czyli po znaku ‚#’. Oznacza to że takie zmiany adresu nie będą wysyłane do servera. Nie będzie full reload. Gdy w przeglądarce wpiszemy

http://www.domain.com/#message/1

to server odpowie tylko na żadanie

http://www.domain.com

Więc po stronie clienta musi oprogramować co się stanie gdy po hashu dostaniemy „message/1”.

Dlaczego routing jest ważny

A po co mi to? Gdy masz taką bardzo client-side aplikację (powiedzmy sklep) w której wszystko robisz javascriptem (bez przeładowań, super płynnie) i jest tam produkt który chcesz wysłać jako linka znajomemu, to musi istnieć właśnie routing. Dzięki temu kopiujesz i wysyłasz superSklep.com.pl/#/products/klawiaturaCODE, a znajomy widzi klawiaturę. Bez tego wysłałbyś superSklep.com.pl i znajomy znalazłby się na głównej i nigdy nie znajdzie klawiatury.

Last but not least, ta technika jest konieczna dla Single Page Applications (dzięki @nilp za komentarz).

Przykładowy kod w Sammy.js

Plik /scripts/app.js:

(function ($) {
    var app = $.sammy(function () {

        this.get('#welcome', function () { // definicja routingu
            alert('You are on "Welcome" page');
        });

    });

    $(function () {
        app.run('#welcome'); // wystartuje i przejdź do adresu #welcome
    });
})(jQuery);

Przykładowy routing. get(…) oznacza, że chodzi o request HTTP GET (są też pozostałe POST, PUT, DELETE). Jako pierwszy parametr dopasowujemy routing (pod spodem są wyrażenia regularne), w tym przypadku dopasuje się tylko #welcome. Drugi parametr to callback, który wykona się po wejściu na taki adres.

Parametry

// routing z parametrami rozdzielonymi przez slashe
this.get('#:name/:age', function () { 
    // zmienne dostępne w "params"
    alert(this.params.name + ' is now ' + this.params.age + ' years old); 
});

Routing który zostanie dopasowany do powyższego przykładu to na przykład „#marcin/14” lub „#paulina/4”. Zostanie też dopasowane „#24/bartek” – nie definiujemy typów. „#/ala/ma/kota” już nie zostanie dopasowane – ilość segmentów musi się zgadzać.

Redirect

this.get('#/by_name/:name', function () {
    // do some work, maybe in Knockout. Angular has its own routing system.
    this.redirect('#', this.params.name, this.params.name);
});

Można też z kodu przekierowywać na wybrane adresy, za pomocą redirect. Redirect przyjmuje parametry, z których stworzy kolejne segmenty. Gdy wejdziemy na adres „#/by_name/marian”, zostaniemy przekierowani na „#/marian/marian”.

Kolejność routingów

(function ($) {
    var app = $.sammy(function () {

        this.get('#:name/:age', function () {
            // to zobaczymy na ekranie
            alert('#:name/:age'); 
        });
        
        this.get('#hardcoded/:age', function () {
            // mimo, że kolejny bardziej pasuje
            alert('#hardcoded/:age'); 
        });
    });

    $(function () {
        app.run('#hardcoded/22');
    });
})(jQuery);

Jak staram się pokazać na powyższym przykładzie – kolejność definiowania routingów ma znaczenie.

Dopasuj cokolwiek

(function ($) {
    var app = $.sammy(function () {
        
        // po prostu wyrażenie regularne
        this.get(/\#\/by_name\/(.*)/, function () { 
            alert(this.params['splat']);
        });
    });

    $(function () {
        app.run('#/by_name/params=can,be|anything&age=100');
    });
})(jQuery);

Jeśli potrzebujemy większej kontroli nad tym co się znajduje w adresie możemy skorzystać ze zmiennej splat . Powyższe wyrażenie regularne dopaje do splat cokolwiek (.*) znalezione to „#/by_name/”. Możemy wtedy z kodu parsować taki adres.

Definiowanie routingów przypomina mechanizm znany z ASP.NET MVC. To pewnie oznacza, że wszystkie routingi działają podobnie, niezależnie od biblioteki.

Na tych postawach zbudowane są ciekawsze rzeczy jak ładowanie widoków i ich automatyczna podmiana. Tutaj ograniczam się do prostego proof of concept.

Kod i przykład do poklikania

Przykład jest prymitywny, operuje na ukrywaniu i pokazywaniu divów, ale działa!

Sammy.j routing example

Sammy.j routing example – app.js

Git aliasy

git minus plus

Klepanie długich komend w konsoli nie jest przyjemne. Klepanie krótkich, a wiele robiących komend jest bardzo przyjemne. Dlatego korzystając z Gita, powinniśmy zainteresować się skrótami (zwanymi aliases).

Aliasy umieszczam w pliku .gitconfig w katalogu użytkownika. Składnia wygląda tak

[alias]
  br = branch 

Można go też dodać z linii komend

git config --global alias.br 'branch' 

Użycie z linii komend

git br

Oczywiście są też aliasy z których jest większy pożytek niż z tego przykładowego. Z niektórych bez aliasu korzystać raczej trudno, jak na przykład

lds = log --pretty=format:"%C(yellow)%h\\ %ad%Cred%d\\ %Creset%s%Cgreen\\ [%cn]" --decorate --date=short

Poniżej moje aliasy na dzień dzisiejszy. Najciekawsze to standup oraz jira. Jira pomaga mi wypełnić jire każdego dnia. Natomiast standup pozwala przypomnieć o czym mam na standupie opowiedzieć.

Gist

To coś powyżej to gist. Pozwala to umieścić w sieci najczęściej proste kawałeczki jakiegoś kodu (lub czegokolwiek, ja umieszczałem słówka norweskie których się uczyłem). Jest to oparte o GitHub więc mamy od razu wersjonowanie.

Piszę więcej o Gist.