Archiv für den Monat Dezember 2012

MVC4 WebAPI und knockoutjs – Teil 2 – Validation


Auch wenn man die WebApi benutzt und AJAX Requests ausführt ist es problemlos möglich die Modelvalidierung von MVC zu benutzen. Dafür müssen wir den View wie bisher erstellen und z.B. auf Attribute wie “Required” in unserem Model zurückgreifen.

Im View muss weiterhin “ValidationMessageFor” für das jeweilige Eingabefeld verwendet werden.

</pre>
<div class="control-group">@Html.LabelFor(model => model.Vorname, new { @class = "control-label" })
<div class="controls">@Html.TextBoxFor(model => model.Vorname, new { data_bind = "value: Vorname" }) @Html.ValidationMessageFor(model => model.Vorname)</div>
</div>
<pre>

Um die native Model Validierung von MVC auszuführen/anzuzeigen, gibt es zwei Möglichkeiten.

1. Validierung für alle Requests automatisch einstellen.

Für diese Variante muss ein FilterAttribut erstellt werden und global für alle WebApi Aufrufe registriert werden.

 /// 
 /// ValidationAttribute für WebApi Zugriffe die nicht valide sind.
 /// 
 public class ValidateAttribute : ActionFilterAttribute
 {
     public override void OnActionExecuting(HttpActionContext actionContext)
     {
         if (!actionContext.ModelState.IsValid)
         {
             actionContext.Response = actionContext.Request.CreateErrorResponse(
                 HttpStatusCode.BadRequest,
                 actionContext.ModelState);
         }
     }
 } 

Registrieren des ValidateAttribute, wird bei mir vor dem Registrieren der Routen gemacht

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //Registrieren unseres Filters für die Modelvalidation für AJAX Requests
        config.Filters.Add(new ValidateAttribute());

        ......

Wenn jetzt ein AJAX Request mit mit einem “Aktuellen” Model stattfindet z.b. mit “Required” Attribute werden die Fehlermeldungen im Webformular angezeigt und es werden keine Daten am Controller auf dem Server “ankommen”. Es wird vom Server ein StatusCode 400 zurückgegeben, der bei Bedarf auch noch ausgewertet werden kann.

2. Validierung nur für bestimmte Controllerfunktionen durchführen.

Hier wird kein ValidateAttribute angelegt, sondern direkt in der jeweiligen Controllerfunktion eine HttpResponseException ausgelöst, wenn das Model nicht valide ist. Anhand dieser Exception, weiß der Client auch das er einen Fehler anzeigen soll.

public PersonModel Save(PersonModel value)
{
    if (ModelState.IsValid)
    {
        if (value == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        //Gibt den Statuscode 200 für OK zurück!
        value.Id = 5;
        return value;
    }

    throw new HttpResponseException(HttpStatusCode.BadRequest);
}

Codeplex:

https://squadwuschel.codeplex.com/

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier dann die Wichtigsten Dateien im Webprojekt unter “Views/Home/Person.cshtml”, “App_Start/WebApiConfig.cs”, “Controllsers/PersonEditApiController.cs” und “Helpers/Filters/ValidateAttribute.cs”

Advertisements

MVC4 WebAPI und knockoutjs – Teil 1 – Modelbinding


Um dynamische und flexible Oberflächen zu erstellen, lohnt es sich einen Blick auf die WebAPI in Verbindung mit knockoutjs zu werfen. Die Webseite von knockoutjs bietet hier bereits einen sehr guten Überblick und eine gute Einführung wie genau knockoutjs funktioniert, daher werde ich darauf nicht im Detail eingehen.

Mit Hilfe von MVC4, WebAPI und knockoutjs lassen sich MVC Oberflächen sehr einfach aktualisieren. Ich versuche es hier mit dem normalen knockoutjs + mapping Plugin, damit ist es möglich direkt das MVC Model in ein knockoutjs Model umzuwandeln.

Das folgende Beispiel zeigt, wie man ein Model erstellt und dann z.B. beim Speichern keinen Postback auslöst, sondern das ganze per Ajax Call abspeichern kann und die Oberfläche aktualisiert.

1. Erstellen eines passenden Models in C#, welches dann in ein JSON Objekt umgewandelt wird.

public class PersonModel
{
        [Display(Name = "ID")]
        public int Id { get; set; }

        [Required]
        [DataType(DataType.Text)]
        [Display(Name = "Vorname")]
        public string Vorname { get; set; }

        [Required]
        [DataType(DataType.Text)]
        [Display(Name = "Nachname")]
        public string Nachname { get; set; }
}

2. Wir nutzen einen normalen Controller und einen normalen View um unsere Daten in der Bearbeiten Ansicht darzustellen.

Wir passen den View aber bereits an unser knockoutjs Model an, d.h. wir Nutzen TextBoxFor, da wir hier zusätzliche HTML Attribute erstellen können. Wir erstellen hier “data_bind” welches dann im HTML als “data-bind” gerendert wird und setzten den passenden Value der aus unserem Model per knockoutjs angezeigt bzw. aktualisiert werden soll.

<div class="control-group">
     @Html.LabelFor(model => model.Vorname, new { @class = "control-label" })
     <div class="controls">
         @Html.TextBoxFor(model => model.Vorname, new { data_bind = "value: Vorname" })
         @Html.ValidationMessageFor(model => model.Vorname)
     </div>
</div>

3. Anlegen unseres API Controllers.

Hier gibt es zwei Funktionen, die beide das “gleiche” machen. Die Daten die wir mit dem Postback erhalten, werden in unser PersonModel “umgewandelt” und wir können können die Daten jetzt Speichern, …. Wir passen aber nur die ID an und geben unser Aktualisiertes Model wieder zurück, damit wir auch auf der Webseite sehen, das sich die ID geändert hat.

public class PersonEditApiController : ApiController
{
    /// <summary>
    /// POST api/PersonEditApi/SavePerson
    /// </summary>
    public HttpResponseMessage SavePerson(PersonModel value)
    {
        if (ModelState.IsValid)
        {
            if (value == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            value.Id = 5;
            //Wird automatisch in JSON Objekt umgewandelt und gibt den StatusCode 201 für Created zurück!
            HttpResponseMessage response = Request.CreateResponse<PersonModel>(HttpStatusCode.Created, value);
            response.Headers.Location = new Uri(Request.RequestUri, "/api/PersonEditApi/SavePerson/" + value.Id.ToString());
            return response;     
        }

        throw  new HttpResponseException(HttpStatusCode.BadRequest);
    }

    /// <summary>
    /// POST api/PersonEditApi/Save
    /// </summary>
    public PersonModel Save(PersonModel value)
    {
        if (ModelState.IsValid)
        {
            if (value == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            //Gibt den Statuscode 200 für OK zurück!
            value.Id = 5;
            return value;
        }

        throw new HttpResponseException(HttpStatusCode.BadRequest);
    }
}

4. Hinzufügen einer WebApi Route “AlternateWebApi”

Damit wir den PersonEditApiController auch so nutzen können wie wir es bisher von den MVC Controllern gewohnt sind, müssen wir noch eine weitere Route zu unserer WebApi hinzufügen. Denn mit der Standardroute für API aufrufe sind nur GET/POST/DELETE Abfragen möglich. Aber meist hat man mehr wie nur diese “paar” Abfragen die man für einen View benötigt, daher fügen wir noch eine neue Route vor der “DefaultApi” Route hinzu.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //Alternatives Routing, hier ist es jetzt wieder möglich gezielte Actionen und Controller abzufragen
        //In der DefaultApi kann man nur Get/Post/Delete abfragen und ist damit sehr begrenzt was die Abfragen angeht.
        config.Routes.MapHttpRoute(
            name: "AlternateWebApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        //Standardroute  - hier lässt sich nur GET/POST/DELETE abfragen.
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }

5. Anpassen unseres JavaScript Codes im View ‘cshtml’, wo die Daten angezeigt werden sollen.

Das Aktuelle C# Model in ein JSON Model Serealisieren und mit dem knockoutjs Modelmapping direkt ein Model erstellen lassen und dieses an unser Formular binden.

Wenn der User den Submit Button klickt, wird unser knockoutjs Model wieder in JSON umgewandelt und vom JSON Format dann in einen String umgewandelt, welcher per API Aufruf übergeben wird. Aktuell werden hier zwei StatusCodes ausgewertet, da wir den Statuscode 200 bei der “Save” Abfrage zurückbekommen und die 201 bei “SavePerson”. Beide Aufrufe führen aber zum gleichen Ergebnis.

(Achtung es gibt auch eine ko.toJSON() Funktion, diese darf hier NICHT verwendet werden, da sonst ein Model erstellt wird welches nicht Valide mit unserem C# Objekt ist und unsere Funktion in der C# API als Model “null” übergeben bekommt.)

var mod12 = ko.mapping.fromJS(@(Html.Raw(JsonConvert.SerializeObject(Model))));
ko.applyBindings(mod12);

function CreatedSuccess(data) {
    ko.applyBindings(data);
}

$("form").submit(function (event) {
    event.preventDefault();
    var model = JSON.stringify(ko.toJS(mod12));

    $.ajax({
        url: "/api/personEditApi/Save",
        type: "POST",
        data: model,
        contentType: 'application/json; charset=utf-8',
        statusCode: {
            200 /* OK */: function (data) {
                CreatedSuccess(data);
            },
            201 /* Created */: function (data) {
                CreatedSuccess(data);
            }
        }
    });
});

 

Aktuell habe ich noch Probleme beim anzeigen und Konvertieren von Datumsangaben beim Modelbinding, daher verwende ich auch “JsonConvert” und nicht die Standard Json.Serialize Funktion von MS, denn dann werden nur Ticks angezeigt und mit JsonConvert wird das ISO Format für das Datum verwendet. Wenn ich diese Problematik in den Griff bekommen habe, wird es ebenfalls einen eigenen Post geben. Bzw. wenn jemand bereits Erfahrungen damit gesammelt hat freue ich mich gern über einen Kommentar :-).

Codeplex:

https://squadwuschel.codeplex.com/

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier dann die Wichtigsten Dateien im Webprojekt unter “Views/Home/Person.cshtml”, “App_Start/WebApiConfig.cs” und “Controllsers/PersonEditApiController.cs”

Quellen:

http://www.west-wind.com/weblog/posts/2012/Aug/21/An-Introduction-to-ASPNET-Web-API

http://www.asp.net/web-api/overview

http://code.msdn.microsoft.com/ASPNET-Web-API-JavaScript-d0d64dd7/sourcecode?fileId=63598&pathId=212749278

http://knockoutjs.com/index.html

LESS mit WinLess im pre build event in CSS umwandeln


Da ich in neuen Projekten Primär mit LESS Arbeite und auch Twitter Bootstrap mit Less arbeitet, habe ich bereits versucht mit “dotless” die LESS Dateien in CSS umzuwandeln. Das mag evtl. mit einfachen LESS Dateien funktionieren, aber die aktuelle Twitter Bootstrap LESS Variante habe ich damit nicht fehlerfrei umgewandelt bekommen.

Dann bin ich durch einen Kollegen auf WinLess gestoßen, hier wird auch eine Möglichkeit geboten die LESS Dateien direkt im pre build event der Webseite in CSS umzuwandeln und das ganz einfach ohne zusätzlich installierte Komponenten.

1. Dateien bei GitHub herunterladen: https://github.com/duncansmart/less.js-windows hier direkt die ZIP Datei herunterladen und entpacken.

2. Am besten irgendwo im Solutionordner ablegen, es werden nur die unten gezeigten Dateien benötigt. Ich habe diese dann noch meiner Solution hinzugefügt, damit ich die Items mit in die Versionsverwaltung bekomme. (Die less-1.3.0.js Datei kann durch die neueste less.js ausgetauscht werden, hier muss nur der Name beibehalten werden.)

imageimage

3. Hinzufügen der PreBuildevents im Webprojekt:

CALL „$(SolutionDir)LessDependencies\lessc“ „$(ProjectDir)Content\less\_myCss.less“ „$(ProjectDir)Content\less\_mybootstrap.css“ –compress

image

In der _myCss.less Datei habe ich per Import die bootstrap.less importiert die dann alle restlichen Bootstrap Dateien importiert, daher muss hier zwingend die “myCss.less” Datei als Quelle angegeben werden. Die “_myBootstrap.css” Datei habe ich im gleichen Verzeichnis liegen, damit die Pfade zu den Bildern passen.

Jetzt wird die CSS Datei immer erstellt wenn das Projekt Compiliert wird und man braucht im Code nur noch auf die CSS Datei verlinken.

MVC Client Datenvalidierung für “dd.MM.yyyy” mit jQuery Globalize


Ich hatte bei meiner MVC Anwendung das Problem, das die Clientseitige Validierung eines Datums aus meinem Model als nicht “richtig” erkannt wurde und damit ist die Clientseitige Validierung fehlgeschlagen. Dabei wurde das Datumsformat in Browser als “dd.MM.yyyy” angezeigt und als ungültiges Datum erkannt, trotz eines gültigen Datums.

Model:

[Required]
[Display(Name = "Jobstartdate", ResourceType = typeof(WorktimeViews))]
[DataType(DataType.Date)]
public DateTime StartDate { get; set; }

Fehlermeldung im Browser:

image

Als erstes habe ich das NuGet Package “jquery-globalize” zu meinem Projekt hinzugefügt, hier habe ich aber nur einige der “Sprachen” im Projekt behalten.

image

Dann habe ich das Bundle für die JavaScript Bibliotheken angepasst und die Globalize Funktionen nach meinen anderen jQuery Bibliotheken eingefügt.

  bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*", 
                        "~/Scripts/jquery.globalize/globalize.js",
                        "~/Scripts/jquery.globalize/cultures/globalize.culture.de-DE.js",
                        "~/Scripts/_myjQueryExtensions.js"));

und in meiner eigenen “_myjQueryExtensions.js” noch die folgenden Zeilen an Code hinzugefügt. In der Document Ready Funktion gibt man noch an welche Sprache aktuell für die Validierung genutzt werden soll.

jQuery.validator.methods.date = function(value, element) {
    return Globalize.parseDate(value);
};

$.validator.methods.number = function(value, element) {
    if (Globalize.parseFloat(value)) {
        return true;
    }
    return false;
};

jQuery(document).ready(function () {
    Globalize.culture("de-DE");
});

Dann wurde mein Datum “dd.MM.yyyy” als gültiges Datum erkannt und es gab KEINEN Validation Error mehr. Selbiges geht wohl auch für Zahlenwerte ob Komma oder Punkt als Tausendertrennzeichen verwendet werden soll. Das ganze lässt sich auch ausbauen, damit die richtige Sprache verwendet wird, je nach Client der sich die Webseite anzeigen lässt in meinem Beispiel habe ich hier hart die “de-DE.js” verwendet.

Probleme gab es bei mir im Chrome v23.xx mit dem Datum hier wurde vom Chrome ein “falsch” formatiertes Datum an die parseDate Methode übergeben. Außerdem gibt es Probleme mit der Range Validation Methode im MVC und Double werten.

Quellen:

http://blog.brainnovative.com/2010/12/globalizing-aspnet-mvc-unobtrusive.html

http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/adding-validation-to-the-model

Entity Framework 4 – Lehrgang und meine “Erfahrungen”


Ich arbeite zwar schon eine ganze Weile mit EF, aber mein bisheriges Wissen bestand zum Großteil aus Wissen was ich mir beim Problemlösen angeeignet habe. Die letzte Woche hatte ich jetzt das Vergnügen an einem MOC Kurs von MS über das Entity Framework teilzunehmen. Die Beispiele waren Teils sehr interessant, aber auch teilweise abschreckend die MS da in seiner Schulung verwendet.

Im folgenden werde ich teils nur ein Paar Stichpunkte nennen, damit ich die Ideen dahinter nicht “vergesse” und evtl. helfe ich dem einen oder anderen auch dabei, wenn ich meine “Gedanken” zu den einzelnen Themen kurz anreise, nur um zu wissen wo nach man bei Google evtl. suchen muss. Es ist durchaus möglich das hier einige Themen bereits “veraltet” sind, da der Lehrgang nur auf EF4 eingeht und nur mit dem “ObjectContext” gearbeitet wird.

Bisher habe ich EF meist in Webprojekten verwendet und hier ist die Lebensdauer eines ObjectContextes nur so lange wie ein Request. Daher fallen hier einem einige Features die EF zu bieten hat gar nicht auf. Dazu gehört z.B. das Change Tracking bzw. das Vorhalten der Daten im EF, wenn diese einmal abgerufen wurden. Diese “neuen” Möglichkeiten können einen aber auch vor Probleme stellen, denn man muss von Anwendung zu Anwendung selbst entscheiden wann die Daten nur aus dem Cache (Context) geladen werden sollen und wann sollen diese frisch aus der DB geladen werden.

ObjectQuery Objekt

Ein wichtiges Element ist hier das “ObjectQuery” hier kann man bei Abfragen festlegen ob die Daten im EF Context überschrieben werden sollen, nur die Änderungen behalten werden oder ob z.B. gar keine Änderungen “NoTracking” gemerkt werden sollen (die Objekte werden vom Context “abgehängt”).

// Create a query from the entityset
ObjectQuery contacts = entities.Contacts;
contacts.MergeOption = MergeOption.NoTracking;

// Define the query
var query = from c in contacts select c;

Weitere Möglichkeiten zum Abfragen von Daten

  • Entity SQL Syntax um z.B. dynamische Abfragen zur Laufzeit zu generieren. Dabei werden Abfragen als String erstellt. Die Syntax sieht der von SQL sehr ähnlich, es werden aber die EntitySets abgefragt. Auch hier wird wieder das “ObjectQuery” Objekt verwendet
// Define the Entity SQL query
var queryString = "SELECT VALUE r from AWorksEntities.Contacts as r WHERE r.ContactID=@contactID";

// Create the query
var query = new ObjectQuery(queryString, entities);
query.MergeOption = MergeOption.OverwriteChanges;

// Add the paramenters
query.Parameters.Add(new ObjectParameter("contactID", contactID));

// Execute the query
List results = query.ToList();
  • Mit “Dynamic Linq” (Expression Trees) lassen sich auch dynamische Abfragen zur Laufzeit erstellen.
  • Stored Procedures können ebenfalls von der DB eingebunden und als Funktionen verwendet werden. Damit diese aber auch als Funktion angelegt werden, müssen diese nach dem Import erst noch für das “Model Freigegeben” werden. Dazu muss man in den Modelbrowser wechseln und im “Store” unter gespeicherte Prozeduren die passende Prozedur heraussuchen. Auch zum Speichern, Updaten oder Löschen können direkt Stored Procedures verwendet werden, diese lassen sich in den Mapping Details zur jeweiligen Klasse festlegen.

image

und mit der rechten Maustaste auf die gewünschte Prozedur gehen und “Funktionsimport hinzufügen” auswählen.

image

Im Fenster dann den Namen der Funktion festlegen, die zugehörige Stored Procedure auswählen und einen Rückgabewert auswählen den die Prozedur liefert. Jetzt kann man einfach über den Context auf die Funktion zugreifen

image

int anz = (int)entities.CountOrders(contactID).First();
  • Refresh von Datensätzen mit der Option ob die Daten aus der DB gewinnen oder die vom Client behalten werden.
entities.Refresh(RefreshMode.StoreWins, contact);
  • Prüfen ob sich einzelne Datensätze geändert haben im Model, dafür muss für jedes Einzelne Property die Eigenschaft “Parallelitätsmodus” (Concurrency Mode) von None auf Fixed gesetzt werden. Dies muss für jedes einzelne Property gesetzt werden welches wir bei Updatekonflikten überwachen wollen.
    Wenn wir das Property auf Fixed gesetzt haben und wir einen DS speichern bei dem ein Konflikt auftritt, wird eine “OptimisticConcurrencyException” geworfen die wir dann auswerten können, z.B. über ein Refresh des Entities lässt sich dieser Konflikt lösen.

image

  • Immer wenn wir SaveChanges aufrufen wird der komplette Speichervorgang für den aktuellen Context in einer Transaktion ausgeführt. Wenn man aber z.B. mehrere Contexte hat die man in einer Transaktion speichern möchte kann man den so genannten “TransactionScope” verwenden, welcher bei einem Fehler eine “TransactionAbordedException” wirft.

Performance von Abfragen “verbessern”

  • Verwenden von “Compiled Queries” um die Queries vorzucompilieren, lohnt sich aber nur, wenn es sich um complexe Queries handelt und wenn man diese nicht nur einmal im aktuellen Context verwendet.
  • Verwenden von “Compiled Views”, damit lassen sich auch die Views optimieren. Der Vorgang muss im Pre-Build des Projektes stattfinden wo sich die edmx Datei befindet. Dazu wird das Tool edmgen.exe verwendet.
  • Verwenden von Eager Loading mit Linq. Dazu muss Lazyloading für den context deaktiviert sein. Dann kann man direkt mit “.Include(“Contacts”) direkte Abhängigkeiten eines Objektes bei Bedarf mit laden.

Allgemein

  • In N-Tier Anwendungen, versenden von Daten z.B. per “Self-Tracking-Entity”, hier muss Lazyloading ausgestellt werden, da ein Serialisierer im schlimmsten Fall die Komplette DB Serialisiert.
  • Synch Framework kann zum Synchronisieren von Client Serverdaten verwenden werden.
  • WCF Data Services für EF, hier werden Abfragen per REST gestellt und Abfragen sind über einfache URL Parameter möglich