ASP.net MVC – Kein Zugriff auf Scripts in der Area

Ein Problem, dass jemanden ein wenig beschäftigen kann, wenn mach versucht ein MVC-Projekt zu strukturieren und alle Dinge, die im Kontext eines Bereichs gültig sind, auch dort zu haben.

In meinem Fall habe ich ein bestehendes MVC-Projekt so umstrukturiert, dass Views, PartialView, Controller, Models und eben auch Scripts, in einem Unterordner einer Area stehen.
Versucht man nun das Script z.B. über Url.Content zu laden, erhält man eine HttpNotFound-Exception. Schuld daran sind – bei mir – 2 Einträge in der Web.config, die im Areas-Ordner hinterlegt ist. Diese steuern, dass man von außen keinen Zugriff auf alle Dateien hat, die unterhalb des Pfads „Areas“ liegen:

<system.web>
<httpHandlers>
<add path=“*“ verb=“*“ type=“System.Web.HttpNotFoundHandler“/>   
</httpHandlers>
</system.web>

<system.webServer>
<handlers>
<remove name=“BlockViewHandler“/>
      <add name=“BlockViewHandler“ path=“*“ verb=“*“ preCondition=“integratedMode“ type=“System.Web.HttpNotFoundHandler“/>
</handlers>
</system.webServer>

Ansich eine gute Sache, schließlich möchte man nicht, dass man Views über die physische URL anspricht. Es verhindert allerdings auch das Laden von Scripts aus diesem und allen Unterverzeichnissen.

Die Lösung ist denkbar einfach:

<system.web>
<httpHandlers>
      <add path=“*.aspx“ verb=“*“ type=“System.Web.HttpNotFoundHandler“/>   
      <add path=“*.ascx“ verb=“*“ type=“System.Web.HttpNotFoundHandler“/>   
</httpHandlers>
</system.web>

<system.webServer>
<handlers>
<remove name=“BlockViewHandler“/>
      <add name=“BlockViewHandler“ path=“*.aspx“ verb=“*“ preCondition=“integratedMode“ type=“System.Web.HttpNotFoundHandler“/>
      <add name=“BlockViewHandler“ path=“*.ascx“ verb=“*“ preCondition=“integratedMode“ type=“System.Web.HttpNotFoundHandler“/>
</handlers>
</system.webServer>

Mit diesen Einstellungen ist nun nur noch der Zugriff auf aspx und ascx-Dateien gesperrt. Scripts können problemlos geladen werden.

Hoffentlich konnte ich dem ein oder anderen ähnliche Probleme ersparen!

Advertisements

Visual Studio: Assembly Version Build und Revision automatisch ändern lassen

Will man eine Assembly kompilierren und weitergeben und wundert sich dabei, dass man immer und immer wieder eine Version 1.0.0.0 weiter gibt, kann man dieses Verhalten ganz einfach steuern.

Dazu genügt eine schnelle Änderung in der Datei „AssemblyInfo.cs“ die im Projekt im Ordner „Properties“ hinterlegt ist.

Scrollt man in dieser Datei ans Ende, findet man 2 Einträge:

  • [assembly: AssemblyVersion(„1.0.0.0“)]
  • [assembly: AssemblyFileVersion(„1.0.0.0“)]

Um bei jedem Build nun die BuildNumber und die Revision automatisch zu aktualisieren, löscht man einfach den 2. Eintrag und ändert für die AssemblyVersion der Wert auf „1.0.*“.

 

ASP.net MVC – ActionResult mundgerecht geliefert / ViewResult to JsonResult

Problemstellung:

Eine Action auf einem MVC-Controller soll je nach Art des Request entweder ein ViewResult oder ein JsonResult zurückliefern. Dabei soll im Accepts-Header geprüft werden, ob ‚application/json‘ explizit angefordert wurde.

Per Default liefert die Action ein ViewResult zurück, bei einem Json-Request soll das Result-Objekt in ein JsonResult-Objekt umgewandelt und das Model aus ViewResult genutzt werden.

Lösung:

Die richtige Stelle, um die Anfrage zu überprüfen, ist der Einstiegspunkt ‚OnActionExecuted‘. Diese kann – und wird für dieses Beispiel – im Controller direkt überschrieben werden.

Natürlich kann man das Ganze auch in ein ActionFilterAttribute auslagern und hier OnActionExecuted überschreiben.

Zunächst wird überprüft, ob der Client im Request angegeben hat, dass er Json erwartet. Diese Information findet man hier:

filterContext.RequestContext.HttpContext.Request.AcceptTypes -> string[]

Für meine Anforderung hat es gereicht, um zu überprüfen, ob der erste Eintrag ‚application/json‘ entspricht und ob das Ergebnis ein ViewResult ist. Ist das der Fall, wird ein neues JsonResult-Objekt erzeugt und das Data-Property auf das Model aus dem ViewResult gesetzt.

Abschließend wird dem filterContext als Result das neu erzeugte Objekt zugewiesen.

string[] acceptTypes = filterContext.RequestContext.HttpContext.Request.AcceptTypes;
System.Web.Mvc.ViewResultBase vr = filterContext.Result as System.Web.Mvc.ViewResultBase;
if (acceptTypes.Length > 0 
    && acceptTypes[0].Equals("application/json") 
    && vr != null){
        filterContext.Result = newJsonResult() { Data = vr.Model };
}

Um es zu testen, habe ich einen Link erstellt, bei dem der click-Event abgefangen wird:

$("a[data-getJson]").live("click", function (evt) {
    $.ajax({  type: "POST",
             url: this.href,
             headers: { 
                 Accept:  "application/json", 
},
              complete: function (data) {
                        alert(data.responseText);
                    }
                });
                evt.preventDefault();
            });

jQuery UI Slider & Fixed Range

Ein Slider ist mit jQueryUI schnell erstellt. Schwieriger wird es erst, wenn man ihn modifizieren möchte.

Mein Problem bestand darin, einen Slider zu bauen, bei dem ein Bereich als optimal hervorgehoben werden sollte.

Ich hab mich dafür entschieden, eine Textbox mit data-Attributen zu versehen um diese dann zur Laufzeit per Javascript zu einem Slider mutieren zu lassen. Dert Wert der Textbox wird dabei als Slider-Starteinstellung genutzt.

Attribute
Name Verwendung Beispiel
data-slider Kommasepariert *1) minimaler und maximaler Wert für den Slider data-slider=“0,10″
data-sliderFixedRange Kommaseparierte *1) Werte für den hervorzuhebenden Bereich data-sliderFixedRange=“5,7″

*1: Das Trennzeichen ist über das jQuery-Plugin $dgSlider_Settings.defaults.valueSplitSign einstellbar.

Die Textbox sollte dann so aussehen:

<input type=“text“ id=“myId“ name=“myName“ data-slider=“0,10″ data-sliderFixedRange=“5,7″ />

Um aus dem Element nun einen Slider zu machen muss einfach $(‚:text[data-slider]‘).dgSlider(); aufgerufen werden.

Abschließend noch das Script:

(function($) {
$.dgSlider_Settings = {
_dataKey: ‚dgSliderDataKey‘,
defaults: {
textbox: null,
slider: null,
valueSplitSign: ‚,‘
} /* end defaults */
}

var methods = {
_init: function(settings) {
$(this).each(function() {
var $this = $(this);
var data = methods._getData($this, settings);

data.textbox = $this;
data.slider = $(„<div />“);
data.slider.insertBefore(data.textbox);
var opt = methods._createOptions(data.textbox);
data.textbox.hide();
data.slider.slider(opt);
});
},
destroy: function() {
$(this).each(function(){
var data = methods._getData($(this));
data.slider.slider(„destroy“);
data.slider.remove();
data.textbox.show();
});
},
/* Erzeugt die Options für den Slider aus den Attributen der Textbox */
_createOptions: function(textbox) {
var data = methods._getData($(this));
var sliderValues = textbox.attr(„data-slider“).split(data.valueSplitSign);
var opt = {
min: parseInt(sliderValues[0]),
max: parseInt(sliderValues[1]),
step: 1,
value: textbox.val()
};

if (textbox.attr(„data-sliderFixedRange“)) {
var fixedRange = textbox.attr(„data-sliderFixedRange“).split(data.valueSplitSign);

var startPoint = (parseInt(fixedRange[0]) * 100) / (opt.max – opt.min);
var rangeWidth = (((parseInt(fixedRange[1]) * 100) / (opt.max – opt.min)) – startPoint);

opt.create = function(evt, ui) {
var rangeDiv = $(„<div/>“).css({
position: ‚relative‘,
left: startPoint + „%“,
width: rangeWidth + „%“,
height: „100%“
}).addClass(‚bg90‘);
$(this).append(rangeDiv);
}
}
opt.change = function(evt, ui) { textbox.val(ui.value); }
return opt;
},
/* Liefert das .data-Objekt */
_getData: function(x, settings) {
var data = x.data($.dgSlider_Settings._dataKey);

if (!data) {
data = $.extend({}, $.dgSlider_Settings.defaults, settings);
x.data($.dgSlider_Settings._dataKey, data);
}
return data;
}

}

$.fn.dgSlider = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === ‚object‘ || !method) {
return methods[„_init“].apply(this, arguments);
} else {
jQuery.error(„Die Methode “ + method + “ konnte nicht gefunden werden“);
}
}

})(jQuery);

ASP.net SignalR

Live, Live, Live… Etwas, was viele von uns im Web vermissen, ist die Möglichkeit, die Kommunikation vom Server zum Client zu ermöglichen. Zum Beispiel dann, wenn ich die Besucher meiner Webseite oder die User innerhalb einer WebApplication immer aktuell mit Informationen versorgen will, ohne Ihnen zuzumuten einen Refresh der Seite zu machen.

Es gibt eine nicht allzu große Anzahl von möglichen Lösungen zu finden, diese unterscheiden sich aber – natürlich – von Browser zu Browser.

Eine wunderbare und vor allem einfach zu implementierende Lösung – auf die ich schon vor einiger Zeit aufmerksam wurde – ist die Komponente SignalR – ein „Freizeitprojekt“ zweier Entwickler aus dem Hause Microsoft.

Auf der Client-Seite brauche ich wenige Zeilen Javascript, auf dem Server, für eine einfache Chat-Funktion, ebenfallt nur einige Zeilen.

Ich will das Ganze kurz im Umfeld einer ASP.net MVC4-Anwendung aufzeigen und ein kleines Chatprogramm schreiben.

Nachdem die Anwendung angelegt wurde, sollten die Referenzen für hinzugefügt werden. Ich mache das über NuGet…

Die Schnittstelle zwischen Server und Client stellt eine Klasse bereit, die von SignalR.Hubs.Hub ableitet. Diese Klasse nenne ich MessageHub

Weiter implementiere ich die Interfaces IConnected und IDisconnect um auf Connect-, Disconnect- und Reconnect-Ereignisse reagieren zu können.

Zusätlich stelle ich 2 Funktionen bereit:

  • JoinGroup: Fügt den Client zu einer Gruppe hinzu
  • SendMessage: Sendet eine Nachricht an alle verbundenen Clients innerhalb der Gruppe

Das Ergebnis sieht dann so aus:

public class MessageHub : Hub, IConnected, IDisconnect
{
// Liste aller verbundenen Clients und der zugeordneten Gruppen
private static Dictionary<string, string> _GroupMembers = new Dictionary<string, string>();

/// <summary>
/// Methode, um einer Gruppe beizutreten
/// </summary>
/// <param name=“group“>Name der Gruppe</param>
public void JoinGroup(string group) {
Groups.Add(Context.ConnectionId, group);
_GroupMembers[Context.ConnectionId] = group;
}

/// <summary>
/// Sendet eine Nachricht innerhalb der Gruppe, in der der Client
/// registriert ist
/// </summary>
/// <param name=“message“>Nachricht, die verteilt werden soll</param>
public void SendMessage(string message)
{
Clients[_GroupMembers[Context.ConnectionId]].receive(message);
}

/// <summary>
/// Wird aufgerufen, wenn der Client getrennt wird
/// </summary>
/// <returns></returns>
public System.Threading.Tasks.Task Disconnect()
{
_GroupMembers.Remove(Context.ConnectionId);
return Clients.leave(Context.ConnectionId);
}

/// <summary>
/// Wird aufgerufen, wenn ein Client verbunden wurde
/// </summary>
/// <returns></returns>
public System.Threading.Tasks.Task Connect()
{
return Clients.joined(Context.ConnectionId, DateTime.Now.ToString());
}

/// <summary>
/// Wird aufgerufen, wenn ein Client erneut verbunden wird
/// </summary>
/// <param name=“groups“></param>
/// <returns></returns>
public System.Threading.Tasks.Task Reconnect(IEnumerable<string> groups)
{
return Clients.rejoined(Context.ConnectionId, DateTime.Now.ToString());
}
}

Auf der Clientseite wird es dann noch viel einfacher. Das SignalR-Framework erzeugt Javascript-Code, der über die URL „~/signalr/hubs“ eingebunden werden muss. Zusätzlich braucht man das JS-Framework, das über den Pfad „~/Scripts/jquery.signalR-0.5.3.js“ verfügbar ist.

Hier kann dann auf die Methoden (joinGroup und sendMessage – ACHTUNG, die Methoden beginnen mit kleinen Buchstaben) zugegriffen werden.

Über jQuery ist SignalR über $.connection erreichbar. Der erstellte Hub fügt sich nahtlos mit seinem Namen in diesen Kontext ein: $.connection.messageHub.

$(function () {
var messageHub = $.connection.messageHub;
messageHub.name = „room1“;
/* Wird aufgerufen, wenn eine Nachricht vom Server gesendet wird */
messageHub.receive = function (message) {
$(„#messages“).append(„<br />“ + message);
}

$(„#send-button“).click(function () {

/* sendet eine Nachricht an den Server*/
messageHub.sendMessage($(„#text-input“).val());
});
/* starten der Connection */
$.connection.hub.start({
/* Callback, wenn die Verbindung aufgebaut wurde */
callback: function (){
/* Einer Gruppe beitreten */
messageHub.joinGroup(messageHub.name);
}
});
}); /* end $(function) */

Und das war es im Prinzip…

Docking Panels mit Javascript

Die Entwicklung einer Applikation für das Web stellt einen doch immer und immer wieder vor die Aufgabe eine Oberfläche zu schaffen, die vom Look and Feel her wie eine Desktop-Anwendung daherkommt.

Da wäre die immer sichtbare Toolbar oben, eine Statuszeile am unteren Rand des Browserfenster – und natürlich sollen beide beim scrollen nicht verschwinden und auch keinen Content verdecken… Das alles wäre ja noch ganz einfach mit fix positionierten DIV-Container zu realisieren, aber was, wenn die Oberfläche aussehen soll wie z.B. ein Email-Client? Oben eine Toolbar, unten eine Statuszeile, dazwischen dann links eine Navigation die 300px der Bildschirmbreite einnehemen soll, der Rest soll sich dann in einen Bereich zum auswählen von Daten, und der Detailansicht aufteilen – und natürlich den ganzen Platz des Bildschirms ausnutzen.

Wenn man sowas in VisualStudio und WinForms realisert gibt es die Möglichkeit Objekte anzudocken. Ein, zwei klicks, fertig… In HTML und JS ist das eher schwierig – es muss aber möglich gemacht werden.

Die Anforderung an meine DockPanels:

  • Ich bin nicht an eine Anzahl Panels gebunden
  • Die Größe muss sich bei einem Resize des Fensters diesem anpassen
  • Ich muss fixe und variable Höhen und Breiten definieren können

Weiterlesen

C#: Operationen asynchron ausführen

Nachdem ich lange nicht’s mehr geschrieben habe dachte ich mir heute, es wird mal wieder Zeit eine Lösung zu einem Problem zu posten. Was mich heute beschäftigt ist, wie ich es schnell und einfach schaffe einen oder mehrer Operationen asynchron auszuführen, ohne mich jedesmal um das Erstellen eines neuen Thread kümmern zu müssen.

Meine Anforderung

war es ein Objekt zu schaffen, dass einen neuen Thread erzeugt und eine Funktion aufruft, die mit dem übergebenen Objekt arbeitet – das Ganze soll dann natürlich auch noch TypeSafe sein und ich möchte mitbekommen, ob die Operation erfolgreich gewesen ist, oder eben nicht.

Der Ansatz

ist denkbar einfach. Eine Klasse – AsyncProcess – stellt eine Funktion Start und einen Event DoWork bereit.

  • Start nimmt das Objekt, mit dem gearbeitet werden soll, entgegen nimmt, plus ein EventWaitHandle – Objekt, um die Fertigstellung der Verarbeitung zu signalisieren.
  • DoWork wird innerhalb eines neuen Threads gestartet und erhält allen notwendigen Parametern, also das Objekt, das für die Verarbeitung benötigt wird, und ein AsyncProcessContext-Objekt, das EventWaitHandle bereit stellt (Mehr zu dem AsyncProcessContext-Objekt nach dem Code-Beispiel).

Das Ganze schaut dann erstmal so aus:

public class AsyncProcess<T> {
    public void Start(T data, EventWaitHandle waitHandle) {
        AsyncProcessContext ctx = new AsyncProcessContext(waitHandle);
        ctx.OnError += new Action<Exception>(handleError); /* Dazu später mehr */
        Thread thread = new Thread(() => DoWork(data, ctx));
        thread.Start();
    }
    public event Action<T, AsyncProcessContext> DoWork;
}

Das AsyncProcessContext ist zum einen dafür gedacht, Objekte, die im Kontext der Verarbeitung benötigt werden, wie das EventWaitHandle, an die aufzurufenden Methode zu übergeben und verfügbar zu haben, zum anderen ist es, für denn Fall, dass später noch das ein oder andere Objekte, z.B. eine Transaction, hinzukommst, schnell erweiterbar.

Der AsyncProcessContext stellt 2 Methoden bereit um die Verarbeitung abzuschließen:

  • Abort(Exception reason) wird aufgerufen wenn eine Exception während der Ausführung aufgetreten ist
  • Complete() signalisiert, dass die Operation fehlerfrei ausgeführt wurde

Diese Methoden machen natürlich nur dann Sinn, wenn man damit auch etwas anfangen kann. Deshalb habe ich dem Kontext noch 2 Events spendiert, die entsprechend ausgeführt und von dem AsyncProcess-Objekt behandelt werden:

  • event Action OnSuccess: Wird ausgelöst wenn Complete() aufgerufen wurde
  • event Action<Exception> OnError: Wird ausgelöst wenn Abort(ex) aufgerufen wurde

Das AsyncProcessContext-Objekt sieht demnach so aus:

public class AsyncProcessContext {
    public event Action OnSuccess;
    public event Action<Exception> OnError;

    private EventWaitHandle WaitHandle;

    public AsyncProcessContext(EventWaitHandle waitHandle) {
        this.WaitHandle = waitHandle;
    }

    public void Abort(Exception reason) {
        if (this.OnError != null) { this.OnError(reason); }
        this.complete(true);
    }

    public void Complete() {
        if (this.OnSuccess != null) { this.OnSuccess(); }
        this.complete(false);
    }

    private void complete(bool hasErrors) {
        this.WaitHandle.Set();
    }
}

Damit erklärt sich dann auch die Zeile ctx.OnError += new Action<Exception>(handleError); in der Start-Methode, welche allerdings wenig Sinn macht, da der Event noch nicht behandelt wird:

void handleError(Exception ex) {
    this.Successful = false;
    this.Exception = ex;
}

Die Properties Successful und Exception dürfen dem AsyncProcess-Objekt natürlich nicht fehlen.

Über diese kann abschließend sehr einfach überprüft werden ob eine Operation erfolgreich gewesen ist, oder nicht – und für den Oder-Fall kommt man auch schnell an die zugrunde ligende Exception.

Der Einsatz

So sieht es dann Live aus:

    EventWaitHandle[] h = new EventWaitHandle[] {
        new EventWaitHandle(false, EventResetMode.ManualReset),
        new EventWaitHandle(false, EventResetMode.ManualReset)
    };

    AsyncProcess<string> a1 = new AsyncProcess<string>();
    AsyncProcess<string> a2 = new AsyncProcess<string>();

    a1.DoWork += (a, ctx) => {
        ctx.Complete();
    };
    a2.DoWork += (a, ctx) => {
        Thread.Sleep(1000);
        ctx.Abort(new Exception("Einfach so..."));
    };

    a1.Start("test", h[0]);
    a2.Start("test", h[1]);

    WaitHandle.WaitAll(h);

    Debug.WriteLine("a1: " + a1.Successful.ToString());
    Debug.WriteLine("a2: " + a2.Successful.ToString());