Archiv der Kategorie: MVC4

ASP.NET MVC und WebAPI über Dependency Injection Instanziieren (Castle Windsor) – Part 2


Wie ich bereits in meinem ersten Artikel zur Serie TDD erwähnt habe, verwende ich das Dependency Injection (DI) Framework Castle Windsor mit ASP.NET MVC um meine Controller entsprechend zu instanziieren.

Bei der Anwendung von DI mit ASP.NET MVC muss man unterscheiden zwischen den Standard Controllern (DefaultControllerFactory) und den WebAPI Controllern (IHttpControllerActivator). Daher ist für den jeweiligen Controller-Typ ist eine eigene Implementierung notwendig.

Was macht das DI Framework eigentlich für uns?

Anhand von Regeln, die man dem DI Container hinterlegt, kann das DI Framework selbstständig Instanzen von Klassen erstellen. Aufgrund dessen, gibt es im Programm nur noch eine zentrale Stelle an der Instanzen von Klassen erstellt werden. Auch hier gibt es Ausnahmen, dazu gehören z.B. einfache Datenhaltungsklassen. Nichtdestotrotz muss jeweils anhand des vorliegenden Problems geklärt werden, ob eine Instanz über DI übergeben werden muss oder evtl. lokal instanziiert werden kann.

In einem ASP.NET MVC Projekt befindet sich die zentrale Stelle für das Einbinden von Klasseninstanzen beim Erstellen eines Controllers. Da der Standard Controller aber parameterlos ist, muss im jeweiligen Controller einfach ein Konstruktor mit den gewünschten Parametern erstellt werden. Damit dieser Konstruktor dann auch aufgerufen und mit den passenden Abhängigkeiten instanziiert wird, muss das Erstellen der Controller von uns überschrieben werden. Das Instanziieren übernimmt dann Castle Windsor für uns, mit den von uns hinterlegten Regeln.

1. ASP.NET MVC DI mit Castle Windsor initialisieren

Wie bereits erwähnt, benötigen wir einen Castle Windsor Container indem wir unsere Regeln für das Instanziieren von Klassen, z.B. Controllern ablegen. Das Ganze muss in der Global.asax im “Application_Start” entsprechend aufgerufen werden.

private static IWindsorContainer Container { get; set; }

protected void Application_Start()
{
    ...
    //Erstellen unserer Castle Windsor Instanz die wir im gesamten Projekt benutzten
    Container = new WindsorContainer();
    //Hinzufügen unserer Konfigurationen zum Kontainer.
    Container.Install(new WebInstaller(connectionString), new LoggingInstaller());
    ...
}

Über den so genannten Installer von Castle Windsor können wir unsere einzelnen Regelpakete für den Container hinzufügen (registrieren). Der WebInstaller sieht z.B in Auszügen folgendermaßen aus und bezieht sich auf eines meiner Projekte. Wie die Regeln genau aussehen können und was zu beachten ist, kann man auch sehr gut in der Castle Windsor Dokumentation nachlesen.

///
/// Installer für alle wichtigen Webverweise
///
public class WebInstaller : IWindsorInstaller
{
    private  string ConnectionString { get; set; }

    ///
    /// Initialize WebInstaller
    ///
    /// Connectionstring übergeben, der für den Webinstaller benötigt wird.
    public WebInstaller(string connectionString)
    {
        ConnectionString = connectionString;
    }

    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        //Unseren EFModelContainer registrieren mit dem passenden Connectionstring dazu, ACHTUNG wir müssen die bereits erstellte Instanz verwenden,
        //da wir darauf auch bei einigen anderen statischen "Variablen" zugreifen, z.B. Rechteverwaltung und Co.
        container.Register(Component.For(typeof(MainModelContainer)).Instance(CurrentHttpContext.GetDataModel()).LifestylePerWebRequest());

        //Alle Queries Registrieren die sich in der Assembly "User.EF.Queries" befinden und jeweils ein passendes Interface enthalten wie
        //"IMemberQueries/MemberQueries" siehe auch: http://docs.castleproject.org/Windsor.Registering-components-by-conventions.ashx
         container.Register(Classes.FromAssemblyContaining().InNamespace("User.EF.Queries").WithService.DefaultInterfaces().LifestylePerWebRequest());

        //Alle Controller Registrieren die IController beinhalten/nutzen.
        container.Register(Classes.FromAssemblyContaining().BasedOn().LifestylePerWebRequest());
        //Alle WebAPI Controller verwenden IHttpController
        container.Register(Classes.FromAssemblyContaining().BasedOn().LifestylePerWebRequest());
        ....
      }
}

Nach dem Castle Windsor soweit vorbereitet ist und unseren Controller entsprechend instanziieren kann, benötigen wir unsere eigene ControllerFactory. Dafür muss eine neue Klasse erstellt werden, die von “DefaultControllerFactory” ableitet und der wir unseren Castle Windsor Container in einem eigenen Konstruktor übergeben können. Mit Hilfe von “this._container.Resolve(controllerType)” entscheidet Castle Windsor selbstständig welcher Controller aufgerufen wird und welche Abhängigkeiten erstellt sowie instanziiert werden müssen.

 public class CustomControllerFactory : DefaultControllerFactory
 {
     private readonly IWindsorContainer _container;

     ///
     /// Den Windsor Container für unser Projekt übergeben
     ///
     /// Unser aktueller Windsor Kontainer
     public CustomControllerFactory(IWindsorContainer container)
     {
         if (container == null)
         {
             throw new NullReferenceException("IWindsorContainer is Null");
         }

         _container = container;
     }

     ///
     /// Erstellen der passenden Controller Instanzen anhand unseres Containers werden die Instanzen automatisch aufgelöst.
     ///
     protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
     {
         if (controllerType == null)
         {
             throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
         }

         //Unseren Controller auflösen per Castle Windsor
         var controller = (IController)this._container.Resolve(controllerType);

         return controller;
     }

     public override void ReleaseController(IController controller)
     {
         //Wenn der Controller "aufgelöst werden soll, dann auch den Controller aus dem Context entfernen.
         if (HttpContext.Current.Items.Contains(CurrentControllerIndexName))
         {
             HttpContext.Current.Items.Remove(CurrentControllerIndexName);
         }

         this._container.Release(controller);
     }
 }

Damit unsere neue Factory zum Erstellen von Controllern auch von ASP.NET MVC “gefunden” bzw. versendet werden kann, muss diese noch registriert werden. Dies geschieht auch in der global.asax im “Application_Start”

//Erstellen unserer CustomControllerFactory zum Instanziieren der Passenden Controller
var customControllerFactory = new CustomControllerFactory(Container);
//Unsere CustomControllerFactory als Standard Factory zum Erstellen von Controllern setzten, damit diese auch von MVC verwendet wird.
ControllerBuilder.Current.SetControllerFactory(customControllerFactory);

Wenn soweit alles eingestellt ist, kann ein MVC Controller z.B. auch folgenden Konstruktor besitzen, denn durch Castle Windsor werden alle Abhängigkeiten entsprechend aufgelöst und instanziiert übergeben.

 [CustomAuthorize]
 public class AccountController : ControllerBaseUser
 {
     #region Member
     ///
     /// Unser Repository für die Mitarbeiterabfragen
     ///
     private readonly IDiMembership _membership;

     ///
     /// User Repository für alle allgemeinen Abfragen fürs Web.
     ///
     private readonly IWebQuerySummary _webQueries;

     ///
     /// Mailverwaltung für die Accountverwaltung
     ///
     private readonly IAccountMails _accountMails;
     #endregion

     #region Initialize
     public AccountController(IDiMembership membership, IAccountMails accountMails, IWebQuerySummary webQueries)
     {
         if (membership == null)
         {
             throw new NullReferenceException("IDiMembership is Null");
         }

         if (accountMails == null)
         {
             throw new NullReferenceException("AccountMails is Null");
         }

         if (webQueries == null)
         {
             throw new NullReferenceException("WebQuery is Null");
         }

         _webQueries = webQueries;
         _accountMails = accountMails;
         _membership = membership;
     }
     #endregion

2. ASP.NET WebAPI mit Castle Windsor initialisieren

Leider funktioniert die DI der Standard Controller nicht bei den WebAPI Controllern. Damit auch die WebAPI Controller mit DI instanziiert werden können sind die folgenden Schritte notwendig.

Dabei kann natürlich auch in der WebAPI auf die gleichen DI Regeln wie bei den Standard Controllern zurückgegriffen werden. Wir müssen nur im WebInstaller aufpassen, dass wir auch eine Regel für die WebAPI Controller angeben, damit diese aufgelöst werden können.

//Alle WebAPI Controller verwenden IHttpController
container.Register(Classes.FromAssemblyContaining().BasedOn().LifestylePerWebRequest());

Dann kann auch der bereits angelegte Container für die Standard Controller auch für die WebAPI verwendet werden.

Die WebAPI benötigt eine eigene Controller Klasse, der wir unseren DI Container übergeben können und in der dann die WebAPI Controller entsprechend aufgelöst werden können.

///
/// Um die webApi auch über IoC nutzen zu können ist folgender Code notwendig.
/// Und dann muss das ganze über "GlobalConfiguration.Configuration" in der Global Assax registriert werden.
/// http://www.cafe-encounter.net/p1316/windsor-di-with-asp-net-webapi
///
public class CustomWebApiController : IHttpControllerActivator
{
    private readonly IWindsorContainer _container;

    public CustomWebApiController(IWindsorContainer container)
    {
        this._container = container;
    }

    public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
    {
        var controller = (IHttpController)this._container.Resolve(controllerType);
        request.RegisterForDispose(new Release(() => this._container.Release(controller)));
        return controller;
    }

    private class Release : IDisposable
    {
        private readonly Action release;

        public Release(Action release)
        {
            this.release = release;
        }

        public void Dispose()
        {
            this.release();
        }
    }
}

Jetzt muss, wie bereits bei den Standard Controllern, unsere neue Klasse nur noch registriert werden, so dass sie die WebAPI Controller Instanzen anlegen kann. Dazu wird wieder in der Global.asax in der “Application_Start” die entsprechende Codezeile eingefügt.

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new CustomWebApiController(Container));

Und schon kann unser WebAPI Controller den folgenden Konstruktor enthalten.

 public class AdminApiController : ApiController
 {
     #region Member
     ///
     /// User Repository für alle allgemeinen Abfragen fürs Web.
     ///
     private readonly IWebQuerySummary _webQueries;
     #endregion

     #region Initialize
     public AdminApiController(IWebQuerySummary webQueries)
     {
         if (webQueries == null)
         {
             throw new NullReferenceException("WebQuery is Null");
         }

         _webQueries = webQueries;
     }
     #endregion
}

CodePlex

Mein bereits angefangenes Projekt findet Ihr wie immer unter Codeplex –> DiTemplates –> UserManagement

Quellen

http://www.cafe-encounter.net/p1316/windsor-di-with-asp-net-webapi

http://www.devtrends.co.uk/blog/how-not-to-do-dependency-injection-the-static-or-singleton-container

http://docs.castleproject.org/Windsor.Registering-components-by-conventions.ashx

Advertisements

MVC Custom Validation mit IValidatableObject Interface


Letzte Woche wurde ich auf ein Interface aufmerksam gemacht, welches ich bisher in MVC noch nicht benutzt habe. Dabei handelt es sich um das IValidatableObject Interface, mit diesem Interface kann man sehr einfach alle möglichen und komplexen Validierungen für sein Model einbinden. Die Daten können dann an geeigneter Stelle mit “ModelState.IsValid” geprüft werden.

Dafür muss im Model nur das Interface “IValidatableObject” eingebunden werden und mit dem Interface noch die dazu verbundene Funktion “Validate”. In dieser Funktion werden die Daten nach den eigenen Vorstellungen geprüft, nach Abfragen die die Standardattribute wie “Required”, “MinLength”, …nicht abdecken. Dazu gehört z.B. der Vergleich zweier Datumswerte oder Strings. Dazu lassen sich dann Benutzerdefinierte Fehlermeldungen angeben und bei welchen Eingabefeldern diese Fehlermeldungen angezeigt werden sollen.

public class CustomValidationModel : IValidatableObject
{
    [Required]
    public string Name { get; set; }

    [Required]
    [DataType(DataType.Date)]
    public DateTime Birthdate { get; set; }

    [Required]
    [DataType(DataType.Date)]
    public DateTime Deathdate { get; set; }

    [Required]
    public int MinValue { get; set; }

    [Required]
    public int MaxValue { get; set; }

    public void Initilize()
    {
        Birthdate = DateTime.Now.Date;
        Deathdate = DateTime.Now.Date.AddYears(100);
    }

    /// 
    /// Die Custom Validate Methode die durch das Interface eingebunden wird.
    /// 
    public IEnumerable Validate(ValidationContext validationContext)
    {
        if (Birthdate.Date < DateTime.Now)
        {
            yield return new ValidationResult("Das Geburtsdatum muss in der Zukunft liegen", new[] { "Birthdate" });
        }

        if (MinValue  10 && MinValue > 10)
        {
            //Ausgeben der passenden Fehlermeldung und angeben bei welchen Werten die Fehlermeldung angezeigt werden soll
            yield return new ValidationResult("MaxValue und MinValue dürfen nicht größer wie 10 sein.", new[] { "MinValue", "MaxValue" });
        }

        if (Name.Trim().Length > 3 && !Name.StartsWith("Sq"))
        {
            yield return new ValidationResult("Wenn der Name länger wie 3 Zeichen ist, muss dieser mit 'Sq' beginnen.", new[] { "Name" });
        }
    }
}

Dazu muss im Controller nur noch “ModelState.IsValid” geprüft werden und schon haben wir unsere eigene Validierung eingebunden.

public ActionResult CustomValidation()
{
    CustomValidationModel model = new CustomValidationModel();
    model.Initilize();
    return View("CustomValidation", model);
}

[HttpPost]
public ActionResult CustomValidation(CustomValidationModel model)
{
    if (ModelState.IsValid)
    {
        //TODO Valides Model - Aufgaben ausführen
    }

    return View("CustomValidation", model);
}

Im View selbst können die Fehlermeldungen wie gewohnt direkt über “Html.ValidationMessageFor()” bei jedem Eingabefeld ausgegeben werden oder über das “Html.ValidationSummary()” global angezeigt werden.

</pre>
<div class="control-group">@Html.LabelFor(model => model.Name, new { @class = "control-label" })
<div class="controls">@Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name)</div>
</div>
<pre>

 

Der Nachteil an dieser Lösung ist, das leider keine direkte Möglichkeit besteht die Serverseitige Validierung mit der Clientseitigen Validierung zu verbinden. Wer also Serverseitige und Clientseitige Validierung in MVC mit Bordmitteln verbinden möchte, der sollte sich zum ValidationAttribute und dem zugehörigen Interface IClientValidatable z.B. hier belesen.

Codeplex:

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier findet Ihr dann die Wichtigsten Dateien im Webprojekt unter “Views/Home/CustomValidation”, “Models/HomeModels/CustomValidationModel”.

MVC 4 Web API Custom Authorize Attribut


Wie bereits in meinem letzten Artikel erwähnt, benötigt man für Web API ein eigenes Authorize Attribut aus einem anderen .NET Namespace. Das hier verwendete Authorize Attribut kommt aus dem Namespace “System.Web.Http.AuthorizeAttribute”.

Auch hier bietet Microsoft wieder eine Standardlösung an, die nur prüft ob der User eingeloggt ist, dafür muss wie bisher gewohnt im Web API Controller das Authorize Attribut aus dem oben genannten Namespace verwendet werden.

Gibt man mit seinen Web API Aufrufen aber Nutzerspezifische Daten zurück, die nicht jeder eingeloggte User abrufen darf, wird hier wieder ein eigenes Authorize Attribut benötigt. Hier handelt es sich um den gleichen Aufbau wie beim normalen Authorize Attribut. Man kann wieder auf den Controler, Action, Id und sonstige Routing Informationen zugreifen und dann entsprechend im Custom Authorize Attribut darauf reagieren. Damit man weiß welcher User eingeloggt ist muss auch hier wieder das “FormsAuthentication.SetAuthCookie” beim normalen Seitenlogin benutzt werden.

Ich möchte zusätzlich noch darauf hinweisen, das ich für meine Web API Aufrufe nicht die Standardroute verwende. Damit wir die Web API 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 habe ich noch eine neue Route vor der “DefaultApi” Route hinzugefügt.

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());

        //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 }
        );
    }
}

Unser Benutzerdefiniertes Web API Authorize Attribut sieht dann folgendermaßen aus und hier darauf achten das man das richtige Authorize Attribut aus dem richtigen Namespace einbindet!

using AuthorizeAttribute = System.Web.Http.AuthorizeAttribute;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomWebApiAuthorizeAttribute : AuthorizeAttribute
{
    #region Member
    private HttpActionContext CurrentContext { get; set; }
    #endregion

    /// <summary>
    /// Prüfen ob der User auch Autorisiert ist, auf die passenden API Aufrufe zuzugreifen
    /// </summary>
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        //Wenn der User nicht eingeloggt ist, dann wird er hier schon mit einem 401 abgewiesen
        base.OnAuthorization(actionContext);

        //Der Filtercontext wird benötigt um auf die RequestDaten zuzugreifen, z.b. auf die UserId die zugegriffen
        CurrentContext = actionContext;
       

        //Wenn kein User eingeloggt ist, dann hat er auch keinen Zugriff.
        if (!HttpContext.Current.User.Identity.IsAuthenticated)
        {
            //Wenn der User nicht eingeloggt ist, auf die Loginseite verweisen
            HandleUnauthorizedRequest(actionContext);
        }
        else
        {
            //Prüfen der passenden Rechte für die einzelnen Actions/Methoden
            //Wenn der User nicht für den View/Action authentifiziert ist, dann auf die Loginseite verweisen.
            if (!CheckRights())
            {
                HandleUnauthorizedRequest(actionContext);
            }
        }

        //Wenn alles i.o. ist "nichts" unternehmen und einfach beim Aufbau der Seite weitermachen.
    }

    /// <summary>
    /// Den unautorisierten Eingriff, "abwehren"
    /// </summary>
    /// <param name="actionContext"></param>
    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        throw new HttpResponseException(challengeMessage);
    }

    #region Private Functions
    /// <summary>
    /// Prüfen um welchen CurrentController es sich handelt und die passende Sicherheitsprüfung vornehmen
    /// Hier werden Nur API Controller überprüft
    /// </summary>
    /// <returns>TRUE->Darf zugreifen | FALSE->Darf nicht zugreifen</returns>
    private bool CheckRights()
    {
        //Setzen des aktuellen Actionnamen, der aufgerufen wird.
        string actionName = CurrentContext.ActionDescriptor.ActionName;
        //Laden des Controllernamens über den der API Aufruf kam
        string controllerName = CurrentContext.ControllerContext.ControllerDescriptor.ControllerName;
        //Auslesen der ID, wenn eine in der URL mit übergeben wurde.
        string id = CurrentContext.ControllerContext.RouteData.Values.ContainsKey("id") ?
                    CurrentContext.ControllerContext.RouteData.Values["id"].ToString() : null;

        //Den aktuellen User ermitteln der den API Aufruf gestartet hat.
        string username = HttpContext.Current.User.Identity.Name;

        //TODO Prüfung vornehmen ob der Aktuelle User auf die ermittelten Resourcen zugreifen darf.

        return true;
    }
    #endregion
}

Das Attribut kann dann wieder problemlos über dem passenden ApiController verwendet werden

[CustomWebApiAuthorize]
public class WorktimeApiController : ApiController
{
    /// <summary>
    /// Filtert unsere Arbeitszeiteinträge nach den letzten Einträgen die vorgenommen wurde.
    /// </summary>
    /// <param name="id">Die zugehörige UserId zu der die Einträge gefiltert werden sollen</param>
    /// <param name="value">Unsere Modeldaten nach denen gefiltert werden soll</param>
    public HttpResponseMessage ByEntryFilter(long id, WorktimeEntryFilterModel value)
    {
        if (value != null)
        {
            WorktimeListModel worktime = new WorktimeListModel(id, value);
            //Den passen Listentyp speichern, wenn der jeweilige Filter ausgewählt wurde.
            worktime.SaveDefaultListType(id, EWorktimeListTypes.ByEntries);
            worktime.FilterWorktimeList();

            //Wird automatisch in JSON Objekt umgewandelt und gibt den StatusCode 200 für OK zurück!
            HttpResponseMessage response = Request.CreateResponse<WorktimeListModel>(HttpStatusCode.OK, worktime);
            return response;
        }

        //Statuscode 400 Zurückgeben
        throw new HttpResponseException(HttpStatusCode.BadRequest);
    }

...

ASP.NET MVC 4 Custom Authorize Attribute


Wie bereits in einem älteren Artikel von mir beschrieben gibt es hier noch einmal eine kleine Auffrischung der Materie inklusive eines Beispiels auf Codeplex.

Wozu ist das Authorize Attribut eigentlich da? Das Authorize Attribut kümmert sich darum, das nur autorisierte (eingeloggte) User den “Internen” Bereich einer Webseite anschauen können, dabei kann es sich z.B. um den Administratorbereich handeln.

Microsoft stellt von Haus aus bereits ein “Authorize” Attribut zur Verfügung was aber standardmäßig nur prüft ob der User auch eingeloggt ist. Wichtig ist hier das beim Login “FormsAuthentication.SetAuthCookie” verwendet wird, wenn der Login erfolgreich war. Denn damit weiß dann auch das Standard “Authorize” Attribut ob der User auf die jeweilige Aktion/Controller zugreifen darf.

Wenn man das “Authorize” Attribut direkt auf den ganzen Controller anwendet, kann man einfach mit einem “AllowAnonymous” Attribut über der jeweiligen Action diese Action wieder “freigeben” und auch ein User der nicht eingeloggt ist kann dann auf diese Action zugreifen.

Im Folgenden Beispiel sieht man ein Beispiel mit dem Standard “Authorize” Attribut, welches auf den gesamten Controller angewendet wird. Damit sich dann ein User auch noch einloggen kann, muss die Login Funktion mit einem “AllowAnonymous” Attribut bestückt werden. Außerdem sieht man bei der Loginmethode die Verwendung von “FormsAuthentication.SetAuthCookie”.

[Authorize]
public class AccountController : Controller
{
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        ViewBag.ReturnUrl = returnUrl;
        return View();
    }

    [AllowAnonymous]
    [HttpPost]
    public ActionResult Login(LoginModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            //ACHTUNG HIER müsste vorher nat. ein DB Abgleich mit den Usern aus der DB stattfinden
            //Aktuell setzten wir hier jeden User als Authorisiert, wenn er einen beliebigen Usernamen und PW angibt.
            FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
            
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }

        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

    public ActionResult LogOff()
    {
        FormsAuthentication.SignOut();

        return RedirectToAction("Index", "Home");
    }

Dann gibt es noch die Möglichkeit ein eigenes Authorize Attribut zu erstellen in dem man von “AuthorizeAttribute” Attribut ableitet (System.Web.Mvc.AuthorizeAttribute).

Ein eigenes Authorize Attribut ist z.B. notwendig wenn man noch zusätzliche Rechte bei einigen Actions oder ganzen Controllern für einzelne User überprüfen muss. Eine Seite die einen eigenen Admin Bereich hat, benötigt eine Extra Prüfung der Rechte für den Admin Bereich, denn hier hat nicht jeder Autorisierte Nutzer auch Zugriff, sondern nur spezielle Nutzer denen das Recht über die eigene Rechteverwaltung zugewiesen wurde. Dafür gibt es dann das eigene Authorize Attribut.

Bei der hier gezeigten Variante setzt Ihr nur das Authorize Attribut auf den jeweiligen Controller oder Action und eine Prüfung welcher User bzw. welche Rolle auf diesen Controller Zugriff hat, wird alles im Authorize Attribut selbst erledigt. Denn dort können wir auch sehen welche Action, Controller, Id oder Custom Route Values gerade aufgerufen wurde und entsprechend eure Rechte prüfen.

Ich finde der Vorteil an dieser Umsetzung liegt vor allem darin das ich eine zentrale Stelle habe an der ich meine Rechte für Alle Controller und Actions verwalten und setzten kann. Ich bin mit dieser Variante bisher sehr gut zurecht gekommen.

using AuthorizeAttribute = System.Web.Mvc.AuthorizeAttribute;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    #region Member
    private AuthorizationContext CurrentContext { get; set; }
    #endregion

    #region Konstruktor
    /// <summary>
    /// Es muss nichts übergeben werden, da controller und action direkt hier ausgelesen werden können
    /// </summary>
    public CustomAuthorizeAttribute()
    {
    }
    #endregion

    #region Public Functions
    /// <summary>
    /// Funktion die ausgeführt wird, wenn das Attribut "genutzt" wird, das Authorisation Attribut wird immer als erstes Attribut ausgeführt.
    /// </summary>
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
        //Der Filtercontext wird benötigt um auf die RequestDaten zuzugreifen, 
        //z.b. auf die Id, Action oder den Controller auf den zugegriffen werden soll.
        CurrentContext = filterContext;
      
        //Wenn kein User eingeloggt ist, dann hat er auch keinen Zugriff.
        if (!HttpContext.Current.User.Identity.IsAuthenticated)
        {
            //Wenn der User nicht eingeloggt ist, auf die Loginseite verweisen
            filterContext.Result = new HttpUnauthorizedResult();
        }
        else
        {
            //Prüfen der passenden Rechte für die einzelnen Actions/Methoden 
            if (!CheckRights())
            {
                //Auf die Startseite verweisen, wenn der User keinen Zugriff auf den Kontent hat, den er angefordert hat.
                filterContext.Result = new HttpUnauthorizedResult();
                //Oder auf eine beliebige Andere Webseite verweisen, wenn man nicht unbefingt die Startseite anzeigen möchte.
                //filterContext.Result = new RedirectResult("~/Home/Index");
            }
        }

        //Wenn alles i.o. ist "nichts" unternehmen und einfach beim Aufbau der Seite weitermachen.
    }
    #endregion

    #region Private Functions
    protected void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        throw new HttpResponseException(challengeMessage);
    }

    /// <summary>
    /// Prüfen um welchen Controller es sich handelt und die passende Sicherheitsprüfung vornehmen
    /// </summary>
    /// <returns>TRUE->Darf zugreifen | FALSE->Darf nicht zugreifen</returns>
    private bool CheckRights()
    {
        //Wir können hier recht einfach ermitteln um welchen Controller oder View es sich handelt den wir gerade 
        //überprüfen wollen - wenn wir erst einmal von der Standardroute ausgehen.
        string controllerName = CurrentContext.RouteData.GetRequiredString("controller");
        string actionName = CurrentContext.RouteData.GetRequiredString("action");
        string id = CurrentContext.RouteData.Values.ContainsKey("id") ? CurrentContext.RouteData.Values["id"].ToString() : null;

        //Den aktuell eingeloggten User ermitteln für den die Rechte geprüft werden sollen 
        //Der wert der im Username steht legen wir beim Login fest
        //bei "FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe)" 
        //wird hier der Username oder auch die UserId angezeigt.
        string userName = HttpContext.Current.User.Identity.Name;
        

        //Prüfen welcher Controller das Authorize Attribut aufgerufen hat und dann schauen welche Action aufgerufen wurde.
        switch (controllerName)
        {
            case "Home":
                //Rechteprüfung für den jewiligen Controller und evtl. auch auf die 
                //jeweilige Action bezogen durchführen
                return CheckHomeRights();
                break;

            case "Person":
                break;

        }

        return false;
    }

    /// <summary>
    /// Prüft alle Rechte für den HomeController und den enthaltenen Actions
    /// </summary>
    /// <returns></returns>
    private  bool CheckHomeRights()
    {
        return false;
    }
    #endregion
}

Aufruf des Custom Authorize Attributes z.B. für einen kompletten Controller

 [CustomAuthorize]
 public class HomeController : Controller
 {
     public ActionResult Index()
     {
         ViewBag.Message = "Modify this ...";

         return View();
     }
...

}

Wenn man jedoch einen WebAPI Controller überprüfen möchte ist dafür wieder ein anderes Custom Authorize Attribut zuständig, das findet Ihr dann in meinem nächsten Blogeintrag.

Codeplex:

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier findet Ihr dann die Wichtigsten Dateien im Webprojekt unter “Views/Home”, “Helpers/Authentication” und “Views/Account”. Das Projekt sollte auch so lauffähig sein.

MVC Trace, Debugging, Routes und Requests im Browser auswerten mit glimpse


Beim Erstellen einer neuen MVC4 Webseite hatte ich, das Problem das der Aufbau der Seite “extrem” langsam war und ich hatte bereits ermittelt, das es an der DB nicht liegt. Daher habe ich nach einer Möglichkeit gesucht, den Seitenaufbau wie früher im Classic ASP.NET mit Trace anzeigen zu können. Dabei bin ich schnell auf das Tool glimpse gestoßen.

Bei glimpse handelt es sich um ein Plugin welches dem MVC Projekt z.B. per Nuget hinzugefügt werden kann. Die Installation per NuGet ist unkompliziert und geht schnell. In der web.config wird ein Eintrag hinzugefügt, mit dem sich glimplse schnell ein und ausschalten lässt. Hier einfach von defaultRuntimePolicy=“On“ auf defaultRuntimePolicy=“Off“ stellen um glimplse komplett zu deaktivieren.

<glimpse defaultRuntimePolicy="On" endpointBaseUri="~/Glimpse.axd">
 ....
</glimpse>

Ein sehr gutes und cooles Video mit den Entwicklern von glimpse findet Ihr hier, im Video wird gezeigt was man mit glimpse alles machen kann.

Wenn man glimpse eingerichtet und aktiviert hat in der web.config und man sein Projekt startet, kann man ganz einfach über die eigene Projekt URL “http://localhost:58901/Glimpse.axd” noch ein “Glimpse.axd” anhängen und die folgende Seite wird angezeigt.

image

Die beiden Links für “Turn Glimpse on” und “Turn Glimpse off” kann man sich am besten in seine Favoriten ziehen. Wenn man jetzt auf “Turn Glimpse on” klickt und auf seine Projektseite geht, siehst man rechts unten im Bildschirm ein kleines “Logo”

image

Klickt man auf das Logo erscheint die glimpse Übersicht und man kann auf alle wichtigen Debuginformationen zugreifen.

Man sieht z.B. welche Route verwendet wurde um die Seite zu laden, dabei sind die gelben Einträge immer die die verwendet/gefunden wurde.

image

Oder man Sieht wie lange es gedauert hat die Seite zu rendern und wo genau die Zeit “vergangen” ist.

image

Viele der Funktionen werden im oben verlinkten Video erläutert und gezeigt. Ich bin bzw. war sehr begeistert von glimpse, es kann einem wirklich gut helfen Fehler zu finden und Daten auszuwerten, von Ajax Aufrufen bis hin zu Remotenutzungen ist alles möglich (siehe Video).

MVC Einführung/Tutorial in Custom Editor Templates inklusive der Einbindung von HTML5 Attributen im Model


Wenn ich bisher für meine Viewdaten eine extra CSS Klasse oder ein weiteres HTML5 Attribut benötigt habe, dann sah das z.B. folgendermaßen aus.

 @Html.TextBoxFor(model => model.CurrentDate, 
                new { @class = "input-small " + WebConstants.DatePickerCssClass, 
                           @Value = Model.CurrentDate.ToString(WebConstants.InputDateTimeFormat),
								type="date" })

Da kann man schnell den Überblick verlieren und außerdem gibt es noch ne Menge HTML5 Attribute die man anwenden kann. Es gibt aber noch eine weitere Möglichkeit das “Problem” anzugehen und die heißt “MVC Editor Templates”.

1. Ein paar Grundlagen zu den MVC Editor Templates

Sobald Ihr die Funktion “@HTML.EditorFor(….)” oder “@HTML.DisplayFor(…)” nutzt greift MVC auf Editor Templates zurück, wenn es keine Custom Templates findet, dann nimmt es seine eigenen die von MS vordefiniert wurden. Einen sehr guten Artikel darüber hat Brad Wilson geschrieben. MVC unterscheidet in “EditorTemplates” zum Bearbeiten von Werten und “DisplayTemplates” zum Anzeigen von Werten.

Die Eigenen Templates können entweder unter dem jeweiligen View abgelegt werden (im Ordner “DisplayTemplates” oder “EditorTemplates”) oder unter dem Shared Folder. MVC prüft dann erst den jeweiligen passenden View Folder, wenn Ihr spezielle Templates für einen View erstellt habt und wenn MVC dort keine findet, wird weiter im Shared Folder nach Templates geschaut.

imageimage

Standardmäßig gibt es für Alle Standarddatentypen wie “Datetime”, “Int32/64”, “String” und “Bool” ein Template. In MVC kann man aber bei den Modeldaten noch ein Attribut “DataType” festlegen und für jeden DataType gibt es ebenfalls noch ein Standardtemplate.

Bei der Verwendung des DataType Attributs ist außerdem zu beachten, das der verwendete DataType Vorrang vor dem Datentyp den Properties selbst hat. Im Folgenden Beispiel wird also nicht das Template “DateTime” verwendet, sondern es wird das Template “Time” aufgerufen.

Das DataType Attribut hat noch weitere Auswirkungen auf unsere Templates, denn das DataType Attribut legt z.B. fest wie der Ausgabewert “vorformatiert” wird. Wenn es sich z.B. um ein Datetime Attribut handelt mit dem DataType “Time” dann wird bei der Ausgabe nur noch die Zeit ausgegeben z.B. “13:34”, oder wenn für DataType “Date” steht, dann wir z.B. “23.04.2013” ausgegeben und beim DataType “DateTime” wird z.B. “23.04.2013 13:34” ausgegeben.

/// 
/// Verwendung des DataType Attributes Time, veranlasst MVC
/// nicht das "DateTime" Template zu nutzen
/// sondern, es wird das "Time" Template genommen.
/// 
[DataType(DataType.Time)]
[Display(Name = "Aktuelle Zeit")]
public DateTime CurrentTime { get; set; }

Wenn man also ein Element mit “EditorFor” erstellt, dann prüft MVC welcher Datentyp dargestellt werden soll und sucht ein passendes Template heraus und wenn es keines vom User findet, werden die eigenen von MVC benutzt.

Die Funktion “EditorFor” bietet aber auch die Möglichkeit zu sagen, welches Template verwendet werden soll, wenn man z.B. spezielle Custom Templates angelegt hat.

</pre>
<div class="control-group">@Html.LabelFor(model => model.Datum, new { @class = "control-label" })
<div class="controls">@* Aufruf des Standardtemplates für unser Datum. *@ @Html.EditorFor(model => model.Datum) @Html.ValidationMessageFor(model => model.Datum)</div>
</div>
<div class="control-group">@Html.LabelFor(model => model.Datum, new { @class = "control-label" })
<div class="controls">@* Aufruf des Custom Templates "Custom.Date" für unser Datum. *@ @Html.EditorFor(model => model.Datum, "Custom.Date") @Html.ValidationMessageFor(model => model.Datum)</div>
</div>
<pre>

Oder man kann bereits im Model festlegen welches Custom Template genutzt werden soll mittels des Attributs “UIHint”

[HtmlTag(Autofocus = true)]
[DataType(DataType.Date)]
[Display(Name = "Geburtstag")]
[UIHint("Custom.Date")]
public DateTime Geburtsdatum { get; set; }

Aber wie sieht ein Editor Template eigentlich aus? Dies kann sehr unterschiedlich sein, von sehr einfachen Varianten die nur eine zusätzliche CSS Klasse setzten, bis hin zu Templates die direkt DiplayValue und EditorValue anzeigen und in die passende Tagstruktur einbinden.

@* Text.cshtml *@
@model String

@* Einfaches Template was nur den Wert wieder in einer Textbox darstellt *@
@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { @class = "span4"})

Es ist auch möglich nicht “@Html.TextBox” zu nutzen und das komplette Control selbst zu “erstellen” als “input”, hier kommt es ganz darauf an was man machen will und wie viel Aufwand man aufwenden möchte. Auch das direkte Einbinden von JavaScript Code ist kein Problem.

@* Custom.Int32.cshtml *@
@model Int32

@* Im Template direkt eine bestimmte Formatierung angeben *@</pre>
<div class="control-group"><strong>Custom Int32 Template</strong> @Html.Encode(ViewData.ModelMetadata.DisplayName):
<div class="controls">@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue)</div>
</div>
<pre>

2. Einbinden von HTML5 Attributen mit Hilfe eines Attributes im Model

Ich bin häufig vom “EditorFor” gewechselt zu “TextBoxFor” um eigene HTML Attribute angeben zu können wie den Type oder um die Formatierung des Values bestimmen zu können (wie im Beispiel ganz zu beginn). Hier verliert man aber sehr schnell den Überblick wo man was im View gesetzt hat. Hier kann das Direkte Setzten der passenden HTML5 Attribute oder beliebiger anderer Attribute direkt im Model weiterhelfen.

Die Umsetzung, dazu ist letztendlich sehr einfach, denn man muss in seiner Attributklasse nur das Interface “IMetadataAware” einbinden und die dazu gehörende Methode “OnMetadataCreated(ModelMetadata metadata)” einbinden und schon werden von MVC die passenden Werte auch im Template zur Verfügung gestellt.

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class HtmlTagAttribute : Attribute, IMetadataAware
{
    /// 
    /// String der den Key für die "AdditionalValues" Collection enthält in der unsere Attributliste liegt.
    /// 
    public const string HtmlTagAdditionalValuesKey = "HtmlTagAdditionalValues";

    /// 
    /// Wird beim Erstellen der Metadaten für Unser Model aufgerufen und setzt in der passenden Collection
    /// unsere AttributCollection auf die wir dann in den Templates zugreifen können.
    ///  
    /// 
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        //metadata.TemplateHint = "Strings";
        metadata.AdditionalValues[HtmlTagAdditionalValuesKey] = OptionalAttributes();
    }

    /// 
    /// Maximale Länge des Eingabefeldes
    /// 
    public int MaxLength { get; set; }

    /// 
    ///  The min and max attributes works with the following input 
    ///  EHtmlInputTypes: number, range, date, datetime, datetime-local, month, time and week.
    /// 
    public int Min { get; set; }

    /// 
    /// Gibt an ob ein MinValues angegeben wurde
    /// 
    public bool HasMinValues { get; set; }

    /// 
    ///  The min and max attributes works with the following input
    ///  EHtmlInputTypes: number, range, date, datetime, datetime-local, month, time and week.
    /// 
    public int Max { get; set; }

    /// 
    /// The pattern attribute works with the following input types: text, search, url, tel, email, and password.
    /// e.g.: pattern="[A-Za-z]{3}"
    /// 
    public string Pattern { get; set; }

    /// 
    /// The hint is displayed in the input field when it is empty, and disappears when the field gets focus.
    /// 
    public string Placeholder { get; set; }

    /// 
    /// Gibt an ob es sich nur um ein Readonly Feld handelt.
    /// 
    public bool ReadOnly { get; set; }

    /// 
    /// Gibt an ob das Feld Disabled sein soll
    /// 
    public bool Disabled { get; set; }

    /// 
    /// Specifies the shortcut key to activate/focus the element
    /// 
    public string AccessKey { get; set; }

    public int TabIndex { get; set; }
    public string CssClass { get; set; }

    /// 
    /// Gibt an ob auf das Element der Autofokus beim Pageload gesetzt werden soll
    /// Achtung sollte nur einmal Pro "seite" / "Model" verwendet werden.
    /// 
    public bool Autofocus { get; set; }

    /// 
    /// Der Jeweilige Inputtype, hier muss selbst darauf geachtet
    /// werden das dieser nicht mit dem .NET Inputtype "kollidiert"
    ///  und zusammenpasst. - HtmlInputTypes Klasse enthält alle gängigen Input Types
    /// 
    public string InputType { get; set; }

    /// 
    /// Erstellt aus den jeweils gesetzten Attributen eine Liste aus Attributname und Value.
    /// 
    /// 
    public Dictionary OptionalAttributes()
    {
        var options = new Dictionary();

        if (MaxLength != 0)
        {
            options.Add("maxlength", MaxLength);
        }

        if (ReadOnly)
        {
            options.Add("readonly", "readonly");
        }

        if (Disabled)
        {
            options.Add("disabled", "disabled");
        }

        if (Autofocus)
        {
            options.Add("autofocus", null);
        }

        if (!string.IsNullOrWhiteSpace(AccessKey))
        {
            options.Add("accessKey", AccessKey);
        }

        if (TabIndex != 0)
        {
            options.Add("tabindex", TabIndex);
        }

        if (!string.IsNullOrWhiteSpace(CssClass))
        {
            options.Add("class", CssClass);
        }

        if (!string.IsNullOrWhiteSpace(InputType))
        {
            //Der Name des Enums entspricht dem Type direkt.
            options.Add("type", InputType);
        }

        if (HasMinValues)
        {
            options.Add("min", Min);
        }

        if (Max > 0)
        {
            options.Add("max", Max);
        }

        if (!string.IsNullOrEmpty(Pattern))
        {
            options.Add("pattern", Pattern);
        }

        if (!string.IsNullOrEmpty(Placeholder))
        {
            options.Add("placeholder", Placeholder);
        }

        return options;
    }
}

Da wir die Werte die wir als Attribute in unser HTML Element aufnehmen wollen als KeyValuePair<string,object> Collection benötigen, gibt es in unserer Attributklasse die Funktion “OptionalAttributes()” die aus unseren Gesetzten Werten die passende Collection baut.

/// 
/// Die Input Types die wir zur Verfügung stellen.
/// 
public static class HtmlInputTypes
{
    public const string Text = "text";
    public const string Password = "password";
    public const string Datetime = "datetime";
    public const string Date = "date";
    public const string Month = "month";
    public const string Time = "time";
    public const string Week = "week";
    public const string Number = "number";
    public const string Range = "range";
    public const string Email = "email";
    public const string Url = "url";
    public const string Tel = "tel";
};

Verwendet werden kann das ganze dann z.B. folgendermaßen in unserem Model. Es muss nur auf die Verwendung des Properties “InputType” unseres Attributs aufgepasst werden, oder eine entsprechende Logik in den jeweiligen Controls eingebunden werden.

[HtmlTag(Autofocus = true, InputType = HtmlInputTypes.Time)]
[DataType(DataType.Time)]
[Display(Name = "Geburtszeit")]
public DateTime BirthTime { get; set; }

[HtmlTag(MaxLength = 5, Min=10, Max=50000, CssClass = "input-xxlarge")]
[Display(Name = "Geld in Euro")]
public int Geld { get; set; }

Und umgesetzt wird das ganze dann entsprechend in jedem Template wo die Attribute gesetzt werden sollen. Wir können auch im Template noch bestimmte Werte für das jeweilige Template automatisch setzten lassen oder neue hinzufügen.

@model DateTime
@using Mvc4WebApiKoTb.Helpers.MvcBasic

@{
    var values = new Dictionary();
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey(HtmlTagAttribute.HtmlTagAdditionalValuesKey))
    {
        values = (Dictionary)ViewData.ModelMetadata.AdditionalValues[HtmlTagAttribute.HtmlTagAdditionalValuesKey];
    }
    
}

@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, values)
@* Custom.Date.cshtml *@
@model DateTime
@using Mvc4WebApiKoTb.Helpers.MvcBasic

@{
    var values = new Dictionary();
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey(HtmlTagAttribute.HtmlTagAdditionalValuesKey))
    {
        values = (Dictionary)ViewData.ModelMetadata.AdditionalValues[HtmlTagAttribute.HtmlTagAdditionalValuesKey];
    }

    var anzeige = string.Empty;
    if (ViewData.TemplateInfo.FormattedModelValue != null)
    {
        DateTime datum = (DateTime) ViewData.TemplateInfo.FormattedModelValue;
        //Hier wird das übergebene Datum direkt Formatiert ausgegeben
        anzeige = datum.ToString("dd.MM.yyyy");
    }
}

<strong>Custom Date Value</strong> 
@Html.TextBox("", anzeige, values)

Damit haben wir ein eigenes Template für DateTime, welches automatisch die passenden Attribute unserem HTML Element hinzufügt oder sogar das Datum selbst formatiert.

3. Quellen und Code auf Codeplex

http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html
http://stackoverflow.com/questions/7313284/how-can-editor-templates-display-templates-recognize-any-attributes-assigned-t
http://www.dotnetcurry.com/ShowArticle.aspx?ID=687

Codeplex:

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier findet Ihr dann die Wichtigsten Dateien im Webprojekt unter “Views/Testtemplate”, “Models/Testtemplates” und “Helpers/MvcBasic”. Das Projekt sollte auch so lauffähig sein.

MVC WebAPI und Serialisieren von Eingabedaten ohne Form Submit


Die Tage hatte ich das Problem, dass ich auf einer Seite noch ein paar Eingabefelder für einen Filter unterbringen musste. Es war mir leider nicht möglich ein zweites Form Tag dafür zu erstellen, daher habe ich die Elemente mit in das Hauptformular übernommen.

Ich benötigte für meinen Ajax Request an die WebAPI aber nur die Eingabefelder für meinen Filter und nicht die gesamten Formulardaten und ich wollte auch nicht jedes Eingabefeld einzeln heraussuchen und die Werte per Hand zusammensetzten.

Wenn man dann weiß das man mit jQuery problemlos Formulare Serialisieren kann und man kurz im Internet sucht, findet man auch schnell eine Möglichkeit wie man nur bestimmte Eingabefelder serialisieren kann ohne dabei das ganze Formular zu nutzen. Ich habe einfach um meine Suchfelder ein DIV mit einer ID gesetzt und dann per jQuery und einem passenden Selector die passenden Daten erhalten.

<input id="currentDate" class="span35" type="date" name="currentDate">

<div id="searchDiv" class="well well-small">
  <div class="btn-block">
    <select id="FilterWorktimeTypeList" class="span42" name="FilterWorktimeTypeList">
   </div>
   <div class="input-append btn-block">
     <input id="filterText" class="span35" type="text" name="SearchText">
     <button id="filterBtn" class="btn" type="button">Filtern</button>
  </div>
</div>

Da currentDate nicht innerhalb unseres SuchDIVs liegt, müssen wir den Inhalt, danach per Hand zu unseren Daten hinzufügen. Wichtig ist, das jedes Eingabefeld einen Namen hat, denn sonst wird es nicht serialisieren. Außerdem kann nicht die Hausmethode von jQuery zum serialisieren verwendet werden, sondern die Funktion “serializeObject” muss verwendet werden und erst in Verbindung mit “JSON.stringify” wird unser Datenobjekt auch von der WebAPI “erkannt”.

//wir suchen alle Inputfelder unterhalb unseres Divs heraus.
var searchDiv = $("#searchDiv :input")
//Wir serealisieren erst unser "Formular" aka unser SuchDiv
var jsonData = searchDiv.serializeObject();
//Da im Suchdiv nicht unser Datum mit enthalten ist, fügen wir es unserem Objekt mit hinzu.
jsonData["Datum"] = $("#currentDate").val();
//Wir müssen unsere serealisieren Daten noch so umwandeln, das diese auch von der WebAPI erkannt werden
jsonData = JSON.stringify(jsonData);

Die Funktion “serializeObject” habe ich im Netz gefunden und funktioniert wunderbar. Die Funktion selbst greift auf die jQuery Funktion “serializeArray” zurück. Diese Standardfunktion reicht leider nicht aus um ein gültiges Objekt zu erstellen, was die WebAPI als Objekt erkennt und umwandelt.

//jQuery Plugin um z.B. direkt Inputfelder und derren Inhalt zu serealisieren als Objekt!
//http://stackoverflow.com/questions/1184624/convert-form-data-to-js-object-with-jquery
$.fn.serializeObject = function()
{
    var o = {};
    var a = this.serializeArray();
    $.each(a, function() {
        if (o[this.name] !== undefined) {
            if (!o[this.name].push) {
                o[this.name] = [o[this.name]];
            }
            o[this.name].push(this.value || '');
        } else {
            o[this.name] = this.value || '';
        }
    });
    return o;
};

Damit die Daten dann auch entsprechend an die WebAPI als Model übergeben werden können benötigen wir ein passendes C# Model für die Daten.

public class WorktimeEntryFilterModel
{
  /// <summary>
  /// Der Text nach dem gesucht werden soll.
  /// </summary>
  public string SearchText { get; set; }

  /// <summary>
  /// Die ausgewählte ID der WorktimeTypeList für die Filter
  /// </summary>
  public int? FilterWorktimeTypeList { get; set; }

  /// <summary>
  /// Der Datumsstring aus dem Model.
  /// </summary>
  public string Datum { get; set; }
}

Wichtig ist, das ich bei meinen WebAPI Aufrufen eine extra Route eingebunden habe in der WebApiConfig, wieso und weshalb das könnt Ihr hier in einem anderen Artikel nachlesen.

Der Beispielhafte Aufruf unserer Daten “jsonData” kann dann z.B. folgendermaßen aussehen.

$.ajax({
   url: "/api/worktimeApi/ByTextFilter/@Model.UserId",
   type: "POST",
   data: jsonData
   contentType: 'application/json; charset=utf-8',
   statusCode: {
        200 /* OK */: function(daten) {
            //Do some fun with the Data
            }
   }
});

und die passende WepAPI Funktion die unsere Daten entgegennimmt und verarbeitet.

/// <summary>
/// Filtert den Eintrag nach den übergebenen Modeldaten
/// </summary>
/// <param name="id">Die UserId nach der gefiltert werden soll</param>
/// <param name="value">Unsere Modeldaten nach denen gefiltert werden soll</param>
public HttpResponseMessage ByTextFilter(long id, WorktimeEntryFilterModel value)
{
    if (value != null)
    {
        WorktimeListModel worktime = new WorktimeListModel(id, value);
        worktime.FilterWorktimeList();

        //Wird automatisch in JSON Objekt umgewandelt und gibt den StatusCode 200 für OK zurück!
        HttpResponseMessage response = Request.CreateResponse<WorktimeListModel>(HttpStatusCode.OK, worktime);
        response.Headers.Location = new Uri(Request.RequestUri, string.Format("/api/WorktimeApi/ByTextFilter/{0}", id.ToString()));
        return response;
    }

    throw new HttpResponseException(HttpStatusCode.BadRequest);
}

Damit ist es problemlos möglich nur kleine Teile eines großen Formulars zu benutzten. Vielleicht existiert auch kein Formular und damit hat man die Möglichkeit die Daten trotzdem problemlos zu serialisieren und zu versendet.