BigBastis Blog About Me & my Digital Lifestyle

7Jul/140

Nice2Know: Search and replace with value from search

Sometimes you want to achieve a simple task like searching for something and replace something else but want the replaced text to contain the found text - sounds complicated, let me show you an example:

I have the input (on the left) and want to transform it to the format on the right:

transform_text

Now imagine you have 90.000 of those entries, so copy & paste is not something you want to do.

Regular Expressions to the rescue

You can easily achieve this using very simple regular expressions, all you need is an editor which supports this functionality like Notepad++ (which I'm using in this example).

Select Search -> Replace... in the menu (or just hit Ctrl + H on the keyboard)

transform_text_nppp

Make sure you select "regular expression" at the bottom. Now you can define a regular expression in the "Find what" textbox.

A short explanation of the search entry above:

In my case I'm looking for a undefined amount of numbers \d+ until a minus - occurs and then again for a undefined amount of numbers \d+. By wrapping this expression in braces () you make the result available to the "Replace with" textbox.

Now we can use the first result \1 of the find-query above to place it inside the "Replace with:" text.

If you want to split the found value and use different find results you can do so by splitting the "Find What" query like this:

transform_text_nppp2

As you can see we have two results inside the "Find what" query, we separate the digits before the minus from the ones after the minus and use the frst entry for the value of the id-parameter and he second for the value of the system-tag.

This is the result of the second replace:

transform_text_nppp3

As you can see this feature is very powerful and your imagination is the only limit  :)

 

 

 

17Mrz/140

How big download portals earn money with your software

crapware_everywhere

I offer a free tool for download on my homepage called “Get My Keys Back” – which helps you recovering your Windows or Office CD Key. I get lots of offers for bundling it with some installers for some $$ for every download.

Since these Installers are pure evil I never signed up for this but now I was curious to see if there are also websites which don’t ask for my permission and bundle it with their installer.

I went to Google.

First thing I noticed was that my own homepage was on the third place in the results overview – I just suck at SEO, I guess. But then I started looking a little bit closer at the other results – and that came really as a surprise to me.

Crapware everywhere

All the hits on the first page (except my own) repacked my software in a crapware installer which will install lots of crapware software on your computer – and by lots I mean tons – and by tons I mean… well, you get it.

Oh look there’s even an ad for my tool.

Google Ad

There is Softsonic for example. They even wrote some kind of short review of my tool, listed a few pros and cons (which are correct by the way) and offer to download my tool with a shiny green button on the bottom of the page:

 gmkb_softsonic

Wheeey – my software is virus free – that’s great let’s download it. But when you click this stylish green download button this is what you’ll get:

 gmkb_softsonic_2

Yes – you’ll get an even BIGGER green download button, because bigger is always better right? But if you click this giant button you will get a crapware installer which will install really annoying software on your computer (more on that soon).

If you look really close on the bottom of the screenshot (red box) you will see a link to my homepage. I guess 99% of the visitors won’t even see that there’s text on that page besides that really big green button.

gmkb_softsonic_3By the way, have you ever noticed the slogan of softsonic? Its “enjoy software!” – I’m not lying, here is a screenshot. They really mean it!

Well, maybe I’m just out of luck here – let’s try a different google hit - winportal:

 gmkb_winportal_

Different provider – same system, you get a small review and a big shiny green download button. Ok, let’s click it:

gmkb_winportal

Wow – what a surprise, another BIGGER green download button – I did not see that coming! At least they tell you that you’re downloading some sort of download manager. And similar to softsonic they offer a link to the website of the author (red box) – but when you click it you’ll just be redirected to the main page of winportal – so our choices as a user are really limited here.

What’s their slogan you ask? It’s “Secure and free Software”, of course! (translated from german)

Ok, ok let’s try a last one – phpnuke.org:

gmkb_phpnuke_

Wait, what? Shareware? Limited features? I SHOULD PURCHASE IT? Seriously? OMG!

Hey look, they say “All downloads are original and not repacked or modified in any way by us.” – that’s a good sign right? Well, they just lie here as they do in the title…

When you click the Download link (wait, where’s that shiny green download button? I’m getting suspicious…) you will get another crapware installer – nice!

Well – at least they don’t have a crappy slogan…

The installers

So maybe you ask yourself “are those download managers really THAT bad?” – no, they are even worse! Let’s have a look at the download manager offered by phpnuke:

After you start the installer everything looks normal so far:

gmkb_phpnuke_2_1

Let’s click next (green button) and see what happens.

gmkb_phpnuke_3

What the heck is “My Search Dial” and why do they want to f**k up all my browsing settings? Oh, you wouldn’t actually see this site if I wouldn’t clicked “custom installation” (Erweitert in german). As you may expect the “normal” user would just click through this window and accept the crapware installation. Ok, let’s see what happens next.

gmkb_phpnuke_4

Hmmmm, Step 2 of 4 again? Strange… and now they’re even mixing languages – not a good sign. Now they’re trying to install “Sweet Page” on my computer  and I have to click on “disagree” (gray button) if I don’t want that – but again, the “normal” user would just click “accept” (green button) to finish this installer as quickly as possible. Ok, next step.

gmkb_phpnuke_5

SO here we are again – Step 2 of 4 AGAIN! The same pattern as in the previous “Step”.

gmkb_phpnuke_6

Maan that is one big Step… 2 of 4 for the third time and also the same pattern – just a differend crapware tool…

gmkb_phpnuke_7

Seriously? Step 2 of 4 AGAIN? Another crapware tool that will mess up my computer… I wonder how many there are to come…

gmkb_phpnuke_8

Oh, finally Step 3 of 4. The tool we actually wanted to install is being downloaded now. Including some ads, of course!

gmkb_phpnuke_9

Step 4 of 4 - we made it! Wait, what? No we didn’t - the actual installation begins NOW! (green button says “install now”) – what have I been doing the whole time? Fine, install now… I just want my keys…

gmkb_phpnuke_10

What the…? Seems like the software this installer is trying to run is not compatible with windows 64bit… So I ran this whole installer, possibly installed lots of crapware which will slow down my computer and the actual tool I wanted to use the whole time isn’t working? – WOW!

(By the way: my original tool is (of course) compatible with all windows versions back to Windows 2000)

By the way: have you noticed, that you never had a button for going back in the installer? So if you by mistake clicked “accept” on one of those crapware tools you can’t just go back and “disagree” on that! – Very clever!

Conclusion

So if you have parents or relatives which always complain about their slow Windows computer because it’s getting slower and slower each day – I guarantee you, you’ll find at least 20 of those crap programs sitting in the system start options.

So back to me – I guess it’s partly my fault, because I don’t have a license which forbids bundling my tool with these crappy installers. Maybe some of you had similar experiences and have some tips for me. I’m willing to learn J

20Feb/140

Facebook buys WhatsApp for freakin $19 billions

If you ask me there's one main reason why WhatsApp is so successful. Sure, they were one of the first messaging apps out there but thats not it. They are successful because they put the user first.

We wanted to spend our time building a service people wanted to use because it worked and saved them money and made their lives better in a small way. [...] We knew we could do what most people aim to do every day: avoid ads. No one wakes up excited to see more advertising, no one goes to sleep thinking about the ads they’ll see tomorrow. We know people go to sleep excited about who they chatted with that day (and disappointed about who they didn’t). We want WhatsApp to be the product that keeps you awake… and that you reach for in the morning. No one jumps up from a nap and runs to see an advertisement. Why we don'sell ads - WhatsApp Blog (2012)

  • Extreemly easy setup
  • available for almost all phones
  • no ads, no popups, no hidden costs
  • well designed and very easy to use

If you think about it, it really could have been something Apple made - which is why i think Facebook and WhatsApp is not a good match. In fact, facebook is quite the opposite to the WhatsApp values. While Facebook tries to increase the amount of advertising you see everywhere WhatsApp uses a slightly different approach:

Advertising isn’t just the disruption of aesthetics, the insults to your intelligence and the interruption of your train of thought. [...] Remember, when advertising is involved you the user are the product. Why we don'sell ads - WhatsApp Blog (2012)

So even when WhatsApp promises that the service will remain as it is now

[...]You can continue to enjoy the service for a nominal fee. You can continue to use WhatsApp no matter where in the world you are, or what smartphone you’re using. And you can still count on absolutely no ads interrupting your communication.[...] Facebook - WhatsApp Blog (2014)

you still need to ask yourself how Facebook plans to return its investment of freakin $19 billions. I think there is only one way it will happen - you and i will become the product up for sale! On the bright side, this opens the doors for some other apps like Threema, which focuses on security.

30Jan/140

Spring Security with custom AuthoritiesPopulator over LDAP

springlogo

Spring offers you a lot of possibilities when it comes to configuration. But sometimes this billions of possibilities can be a real pita! Reacently i had to connect a Spring MVC application to a LDAP authentication server and since the webapp allready run on spring security i decided to keep it that way an use the LdapAuthenticationProvider offered by spring.

When your project is simple enough so you can use the default configurations offered by spring the complete working LDAP configuration can be as simple as this:


<security:authentication-manager>
	<security:ldap-authentication-provider 
		user-search-filter="(uid={0})"
		user-search-base="ou=users"
		group-search-filter="(uniqueMember={0})"
		group-search-base="ou=groups"
		group-role-attribute="cn"
		role-prefix="ROLE_">
	</security:ldap-authentication-provider>
 </security:authentication-manager>
 
 <security:ldap-server url="ldap://localhost:10389/o=mojo" 
	 manager-dn="uid=admin,ou=system" 
	 manager-password="secret" />

It's as simple as that. Unfortunately our webapp doesn't get the roles from the LDAP, it's only used to authenticate the user, the roles are stored in a seperate database. So now we have to configure a seperate custom AuthoritiesPopulator for our users to get roles.

After a few minutes on google and stackoverflow a still haven't found a suitable solution for my little problem, which is very strange since google AND stackoverflow combine all the human knowledge, right? ;-)

I tried several combinations of different configurations and after a few hours i still haven't found a good and complete example of a configuration i needed. I even started to think that my solution was to unusual to find some good examples.

But eventually after A LOT trial and error i managed to get it working. Looking back it's not even complicated in any way, which makes me wonder why i haven't found a complete example on the internet.

So just to document my solution for the next somebody who's searching fo a similar way to implement LDAP in spring security, here's my configuration:

spring-configuration:


<?xml version="1.0" encoding="windows-1252"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd


http://www.springframework.org/schema/security

        http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <http use-expressions="true">
	    [...]
    </http>

	<authentication-manager>
		<authentication-provider ref='ldapAuthProvider'/>
	</authentication-manager>
	
	<beans:bean id="contextSource" 
		class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">

	    <beans:constructor-arg value="${ldap.url}" />
	    <beans:property name="userDn" value="${ldap.username}" />
	    <beans:property name="password" value="${ldap.password}" />
	</beans:bean>
	
	<beans:bean id="userSearch" 
		class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">

		<beans:constructor-arg index="0" value="" />
		<beans:constructor-arg index="1" value="(uid={0})" />
		<beans:constructor-arg index="2" ref="contextSource" />
		<beans:property name="searchSubtree" value="true" />
	</beans:bean>
	
   <beans:bean id="ldapAuthProvider" 
   		class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
     <beans:constructor-arg>
       <beans:bean 
       		class="org.springframework.security.ldap.authentication.BindAuthenticator">
			<beans:constructor-arg ref="contextSource" />
			<beans:property name="userDnPatterns">
				<beans:list>
					<beans:value>uid={0}</beans:value>
				</beans:list>
			</beans:property>
			<beans:property name="userSearch" ref="userSearch"/>
       </beans:bean>
     </beans:constructor-arg>
     <beans:constructor-arg>
       <beans:bean class="my.company.ldap.DatabaseAuthoritiesPopulator">
           <beans:constructor-arg ref="contextSource"/>
           <beans:constructor-arg value=""/>
           <beans:property name="groupRoleAttribute" value="cn"/>
       </beans:bean>
     </beans:constructor-arg>
   </beans:bean>
</beans:beans> 

DatabaseAuthoritiesPopulator.java


import java.util.HashSet;
import java.util.Set;

import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;

public class DatabaseAuthoritiesPopulator extends DefaultLdapAuthoritiesPopulator{

	public DatabaseAuthoritiesPopulator(ContextSource contextSource,String groupSearchBase) {
		super(contextSource, groupSearchBase);
	}
	
    public Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, String username) { 
    	//Get roles from a datasource of your choice
    	Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        authorities.add((new SimpleGrantedAuthority("ROLE_USER")));
        return authorities;
    }
}

The tricky part is to set the "userSearch" property on line 50. This property is optional and it's easy to overlook. If this property is missing no LDAP search is performed and the LdapAuthenticationProvider tries to bind the user directly based on the given base("" in this case).

If you are having trouble testing the LDAP connection try activating DEBUG output for LdapAuthenticationProvider. Just put this logback.xml somewhere on your classpath:


<configuration>
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder 
			by default -->
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
			</pattern>
		</encoder>
	</appender>

	<logger level="DEBUG" additivity="false" name="org.springframework.security">
		<appender-ref ref="STDOUT" />
	</logger>
</configuration>

Now you have all the important information on your STDOUT.

6Sep/130

Java: Validierung und strukturelle Pruefung einer IBAN

Als ich vor einer Weile die Anforderung bekam eine Prüfung für IBANs zu implementieren habe ich gedacht ich bin im 10 Minuten fertig, das Internet ist groß, stimmts? Google ist nur einen Klick entfernt, oder?

Nun ja, nach den ersten Suchvorgängen habe ich den einen oder anderen Beitrag gefunden der einen mehr oder weniger brauchbaren IBAN Validator verlinkt. Doch die Validierung einer IBAN ist im Grunde der einfache Teil, das etwas schwierigere Vorhaben ist aber die Strukturelle Prüfung einer IBAN.

Dazu kann man zwar einige Web-Tools finden, die das bei bedarf erledigen, aber eine ordentliche Library sucht man dafür vergeblich. (Zumindest habe ich nichts gefunden was mir wirklich gefallen hat).

Lösung: selber machen

Zwei Aufgaben waren also zu bewältigen:

  1. Validierung einer IBAN = Berechnen einer Prüfziffer mit einem ähnlichen System wie bei BAR-Codes.
  2. Strukturelle Prüfung einer IBAN = Den Aufbau der IBAN gegen ein fest definiertes Schema prüfen.

Die Aufgabe war dann doch recht schnell abgearbeitet und das Ergebnis könnt ihr euch bei Github anschauen.

Hier ein Aufrufbeispiel:

Die drei Klassen sind in ein Maven-Projekt eingepackt, damit ihr das gleich ausprobieren könnt, ein paar UnitTests sind natürlich auch dabei.

Ich habe auch schon ein paar Kleinigkeiten festgestellt, die ich in nächster Zeit verbessern werde. Wenn ihr noch Vorschläge habt oder eine IBAN findet, die der Validator nicht korrekt behandelt, meldet euch einfach entweder hier im Kommentarbereich oder direkt im Github Bugtracker.

15Mrz/130

Clover: Tabs für den Windows Explorer

clover_screenshot

Ich glaube schon seit Windows 98 habe ich mich gefragt, warum Microsoft dem Windows Explorer keine Tabs spendiert. Auch Apple bekommt es unter Mac OS nicht hin den Finder Tab-Fähig zu machen. Warum eigentlich nicht?

Clover zeigt wie es gehen könnte. Dieses einfache kleine Tool kombiniert alle Windows Explorer Fenster unter einem Dach und lässt sich dabei so flüssig bedienen, dass es schon fast wie eine native Lösung wirkt.

Auf den ersten Blick fällt sofort auf, dass hier einige Teile vom Google Chrome eingeflossen sind. So gleicht das Look and Feel der Tabs und auch deren Bedienung bis ins Detail dem von Chrome. Alles ist super Flüssig und funktioniert komplett ohne Konfiguration sofort so wie man es gewohnt ist.

So lassen sich neue Tabs mit STRG+T öffnen und mit STRG+TAB durchschalten. Mit einem Doppelklick während man die STRG-Taste gedrückt hält wird der gewählte Ordner in einem neuen Tab geöffnet. Die Tabs selbst integrieren sich in die Titelleiste des Fensters und verbrauchen so gut wie keinen Platz.

Optional kann man auch noch (wie auch im Chrome) eine Lesezeichenleiste einblenden lassen, in der man seine Lieblingsordner speichern kann. Ich finde aber, dass es die Optik etwas stört.

Das Icon des Programms könnt ihr ebenfalls verändern, wenn euch das Kleeblatt nicht zusagt. Eine Anleitung dafür gibts auf der Homepage des Entwicklers.

Weitere Infos und Download.

28Feb/131

iPhone Webapp-Links nicht im Safari öffnen

iOS bietet euch eine nette Funktion mit der ihr Webseiten als Verknüpfung auf eurem Homescreen ablegen könnt. Wenn ihr eure Seite darauf noch etwas vorbereitet, könnt ihr sogar euer eigenes Logo hinterlegen und auch die Webseite später im Fullscreen Modus laufen lassen.

Dazu müsst ihr lediglich ein paar Ressourcen hinterlegen:


    <!-- Setzen der Icons für die verschiedenen Auflösungen -->
    <link rel="apple-touch-icon-precomposed" sizes="144x144" href="/Content/grafik/icons/apple-touch-icon-144-precomposed.png">
    <link rel="apple-touch-icon-precomposed" sizes="114x114" href="/Content/grafik/icons/apple-touch-icon-114-precomposed.png">
    <link rel="apple-touch-icon-precomposed" sizes="72x72" href="/Content/grafik/icons/apple-touch-icon-72-precomposed.png">
    <link rel="apple-touch-icon-precomposed" href="/Content/grafik/icons/apple-touch-icon-57-precomposed.png">
    <!-- sorgt dafür dass die App im Vollbild ausgeführt wird -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <!-- Farbe der Statuszeile -->
    <meta name="apple-mobile-web-app-status-bar-style" content="black">


Safari erkennt dann diese Tags und zeigt euer Icon und führt die Seite dann auch im Vollbildmodus aus. Ein weiteres Goodie ist, dass die Webseite als "App" läuft und im Taskmanager sogar ein eigenes Icon bekommt.

Hier am Beispiel meiner kleinen F1-Tippspiel Seite:

Diese Diashow benötigt JavaScript.

Der Haken
Ein kleines Problem bleibt dann aber noch. Sobald ihr einen Link auf die Seite setzt und der Benutzer diesen antippt wird der Link ein einer neuen Safari Instanz geöffnet. Das Liegt daran, dass iOS euere Seite als App betrachtet und alle Links werden in iOS vom Safari behandelt.

Zum Glück kann man dieses Verhalten sehr einfach mit etwas Javascript umgehen:


<script>(function(a,b,c){if(c in b&&b[c]){var d,e=a.location,f=/^(a|html)$/i;a.addEventListener("click",function(a){d=a.target;while(!f.test(d.nodeName))d=d.parentNode;"href"in d&&(d.href.indexOf("http")||~d.href.indexOf(e.host))&&(a.preventDefault(),e.href=d.href)},!1)}})(document,window.navigator,"standalone")</script>

Weitere Infos und Quelle zu der Lösung findet ihr bei Stackoverflow.

Nun werden alle Links in eurer "App" geöffnet und alles funktioniert wie gewohnt :)

29Jan/130

Nice 2 Know: Razor ViewEngine ausserhalb von MVC nutzen

Wenn ihr ASP.NET MVC ab Version 3 schon mal benutzt habt dann wird euch die Razor ViwEngine kein Fremdword mehr sein. Oft kommt man aber gar nicht drauf, dass man diese praktische Templating-Engine auch außerhalb von MVC benutzen kann.

So kann man dieses Feature super gebrauchen wenn die App E-Mails versenden soll und man größere E-Mail Templates nutzen will. Das Schöne an der Razor Engine ist, dass man diese sehr einfach integrieren kann und der Code sehr verständlich und sauber bleibt.

Die Installation erfolgt hier wie so oft sehr einfach über NuGet:

Jetzt ist man im Grunde schon fertig und kann sofort loslegen. Starten wir mit dem Model, das die Razor Engine für uns verarbeiten wird. Dies ist eine gewöhnliche Klasse (POCO):


public class TipReminder
{
	public string DisplayName { get; set; }

	public string EmailAdress { get; set; }

	public DateTime LastLogin { get; set; }
}

Jetzt legen wir uns ein kleines Template an, das den Inhalt der Email darstellen soll:


@model Projekt.Domain.AppUser
<!DOCTYPE html>

<head>
    <meta name="viewport" content="width=device-width" />
    <title>Newsletter</title>
</head>
<body>
    <div>
        <h2>Hallo @Model.DisplayName</h2>
        <p>Es ist wieder Zeit für einen Newsletter</p>
        <p>Dein letzter Login war am @Model.LastLogin - das ist schon lange her!</p>
        <p>Schau doch mal wieder vorbei.</p>
        <hr />
        <p>Bitte antworten Sie nicht auf diese EMail</p>
        <p>Viel Spaß weiterhin!</p>
    </div>
</body>
</html>

Man beachte hier besonders die Zeilen 10 und 12 wo wir auf das Model zugreifen.

Wenn ihr ASP.NET MVC 3 oder 4 schon mal genutzt habt, werdet ihr feststellen, dass alles exakt der Syntax einer View gleicht, sogar die Definition der strickten Typisierung auf das Model oben.

Die eigentliche Nutzung ist dann ein Kinderspiel:


foreach (AppUser user in db.Users)
{
    string renderedEmail = Razor.Parse(File.ReadAllText(pathToEmailTemplate), user);
    EMailHelper.SendEmail(renderedEmail, user.EmailAdress);
}

Wirklich interessant ist hier nur Zeile 3 wo wir den eigentlichen Aufruf an die RazorEngine tätigen. Die Parse-methode benötigt lediglich das Template als String und ein Model zur Verarbeitung.

Das ist ein wirklich praktisches Feature um die Templates auf einfachste Weise aus dem Programm herauszulösen. Das Ganze ist nicht neu (gibt’s schon seit fast 2 Jahren) man muss nur wissen, dass es möglich ist. :-)

2Nov/120

Nuetzliche Maven Parameter

Maven ist ein sehr mächtiges und praktisches Werkzeug, aber manchmal ist das vordefinierte Verhalten etwas nervig. Glücklicherweise kann man so ziemlich alles über Parameter beeinflussen.

Test-Fehler ingnorieren

Oft will man alle Testfälle eines Projekts ausführen um einen Überblick zu erhalten, Maven bricht per Default aber nach dem ersten Testfehler ab, dieses Verhalten kann man Maven ganz leicht über diesen Parameter abgewöhnen:

mvn test -Dmaven.test.failure.ignore=true

Wenn man will kann man dieses Verhalten auch für ein Projekt vorschreiben, sodass es bei jedem Testdurchlauf so bleibt. Dazu definiert man folgendes Plugin:


  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
      <testFailureIgnore>true</testFailureIgnore>
    </configuration>
  </plugin>

Tests beim install überspringen

Standardmäßig führt Maven beim install auch alle Tests aus und bricht bei dem ersten nicht bestandenen Test den install ab. Dies kann je nach Projektbröße auch eine ganze Weile dauern. So kann es viel Zeit sparen wenn man die Tests weglässt. Dieses einfache Kommando hilft uns hier aus:

mvn install -Dmaven.test.skip=true

Habt ihr noch nützliche Maven Parameter? Lasst es mich wissen. .)

8Okt/122

ASP.NET MVC: Aktuellen Menülink hervorheben

Dieses "Problem" müsste eigentlich jeder kennen der schon mein eine Webseite mit mehreren Bereichen erstellt hat. Wie kann ich den Menüpunkt der aktuell angezeigten Seite hervorheben?

Hier gibt es viele Ansätze. Ich hatte schon Seiten, die die CSS Klassen ins HTML hardcoden, irgendwelche anderen Schandtaten betreiben oder nicht wirklich schöne If-Abfragen um die Menüpunkte legen.


            <li @if (activeMenu.Equals("Home"))
                {
                    <text>class="active"</text>
                }>@Html.ActionLink("Home", "index", "home")</li>

Hier muss man natürlich noch bedenken, dass ein entsprechender ViewBag-Eintrag namens "activeMenu" in jeder Action gesetzt werden muss. urgs

Ich möchte diese ganze Logik nicht in den Views haben. Daher habe ich eine kleine ExtensionMethot für den HtmlHelper geschrieben.

Diese Extension generiert für mich die Menülinks und prüft beim Generieren des Links ob das Ziel des Links mit dem aktuellen Controller und der aktuellen View übereinstimmt. Wenn das der Fall ist wird dieser Link als aktiv markiert. So sieht das Ganze aus:


public static MvcHtmlString NavigationActionLink(
    this HtmlHelper htmlHelper, string linkText,
    string actionName, string controllerName, object routeValues = null,
    object htmlAttributes = null, 
    string wrapperElement = "li", string flag = "active")
{
    var generatedLink = HtmlHelper.GenerateLink(
                    htmlHelper.ViewContext.RequestContext,
                    htmlHelper.RouteCollection, linkText,
                    (string)null, actionName, controllerName,
                    new RouteValueDictionary(routeValues), 
                    (IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));

    var wrappedElement = WrapGeneratedLink(
                    htmlHelper, actionName, 
                    controllerName, wrapperElement, flag, generatedLink);

    return MvcHtmlString.Create(wrappedElement.ToString(TagRenderMode.Normal));
}

private static TagBuilder WrapGeneratedLink(
    HtmlHelper htmlHelper, string actionName, string controllerName,
    string wrapper, string flag, string generatedLink)
{
    var wrapperElement = new TagBuilder(wrapper)
                                {
                                    InnerHtml = generatedLink
                                };

    if (CurrentRouteMatchesGeneratedUrl(actionName, controllerName, htmlHelper))
    {
        wrapperElement.AddCssClass(flag);
    }
    return wrapperElement;
}

private static bool CurrentRouteMatchesGeneratedUrl(
    string actionName, string controllerName, HtmlHelper htmlHelper)
{
    var currentAction = (string)htmlHelper.ViewContext.RouteData.Values["action"];
    var currentController = (string)htmlHelper.ViewContext.RouteData.Values["controller"];

    return string.Equals(currentAction, actionName, 
                        StringComparison.CurrentCultureIgnoreCase) &amp;&amp;
            string.Equals(currentController, controllerName, 
                        StringComparison.CurrentCultureIgnoreCase);
}

Hier der Code nochmal etwas schöner formatiert.

Dieser Helper orientiert sich an dem gewöhnlichen ActionLink und hat die gleichen Parameter bis auf die letzten zwei, mit denen man bestimmen kann wie der Link hervorgehoben werden soll.

So könnte man nun die Menülinks verwenden am Beispiel Twitter Bootstrap:


<div class="navbar navbar-inverse navbar-fixed-top">
    <div class="navbar-inner">
        <div class="container">
            <div class="nav-collapse">
                <ul class="nav">
                @Html.NavigationActionLink("meinlink", "index", "home")
                @Html.NavigationActionLink("meinlink", "index", "account", null, null, "div", "selected")
                </ul>
            </div>
        </div>
    </div>
</div>    

So würde der erste Link folgendes generieren:


<li class="active"><a href="/">meinlink</a></li>

Und der zweite Link das hier:


<div class="selected"><a href="/">meinlink</a></div>

Auf diese Weise kann mann etwas Einfluss darauf nehmen was da generiert wird und mit welcher Klasse das erzeugte Feld markiert wird.

Der zweite Link macht natürlich keinen Sinn in dem Twitter Bootstrap Beispiel und wurde nur zu Demonstrationszwecken genutzt :)

So entfernt man den hässlichen Code aus den Views und muss sich darum keinen Kopf mehr machen.

Übrigens: In dem meisten Fällen reicht es wenn man nur nach dem Controller prüft, da häufig mehrere Actions zu einem Menüpunkt gehören und man dann einen Menüpunkt mit einem Controller abdeckt.

Wie macht ihr das?

Wie ist eure Methode um diese kleine Problematik zu lösen? Habt ihr vielleicht einen schöneren/einfacheren Ansatz? Dann würde ich mich sehr freuen wenn ihr mich belehren würdet :)

Get Adobe Flash player