Liens
MVC
Model |
- Modèle de données
- Accès aux données (bdd, webservice)
|
View |
|
Controller |
- en fonction de l'url, choisit le Model et la View
|
Controller
Convention de routage: /Home/Index
- Classe HomeController
- Méthode Index
Controllers/HomeController.cs
|
[HandleError]
public class HomeController : Controller
{
[ActionName("Index2")] // Alias
[HttpPost]
[HandleError] // Action filter
public ActionResult Index(string id) // permet de récupérer l'id passé à l'url
{
// il est possible d'ajouter dynamiquement n'importe quelle propriété à ViewBag
ViewBag.SomeProperty = "Some message";
return View();
var model = new Model();
model.SomeProperty = "Some message";
return View(model);
var safe_id = Server.HtmlEncode(id);
var controller = RouteData.Values["controller"];
var action = RouteData.Values["action"];
var id = RouteData.Values["id"];
return Content("Ok!");
|
MapRoute
 |
Les routes sont traités dans leur ordre d'apparition. |
App_Start/RouteConfig.cs
|
routes.MapRoute(
name: "New",
url: "new/{name}", // correspond à toutes les url commençant par new
defaults: new { controller = "New", action = "Search", name = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default", // Route name
url: "{controller}/{action}/{id}", // URL with parameters
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
|
Bind
Include, Exclude
 |
Permet de contrôler quelles propriétés sont éditables.
Prévient le problème d'overposting ou de mass assignment, où une propriété qui n'est pas dans le formulaire est affectée (via un paramètre dans l'url par exemple) |
PersonController.cs
|
public ActionResult Create([Bind(Include = "Name,Adress")] Person person)
{ }
public ActionResult Create([Bind(Exclude = "Phone")] Person person)
{ }
|
Prefix
|
@Html.ActionLink("Text", "Index", new { id = item.Id })
|
PersonController.cs
|
public ActionResult Index([Bind(Prefix="id")] int personId)
{ }
|
ActionResult
Type
|
Description
|
Méthode
|
ViewResult PartialViewResult |
La réponse est générée par le view engine. |
View() PartialView()
|
ContentResult |
Retourne un string. |
Content()
|
EmptyResult |
Pas de réponse générée. |
-
|
FileContentResult FilePathResult FileStreamResult |
Retourne le contenu d'un fichier. |
File(Server.Path("~/Dossier/Fichier.ext"), "text/txt")
|
HttpUnauthorizedResult |
Retourne un status HTTP 403. |
-
|
JavaScriptResult |
Retourne un script à exécuter. |
JavaScript
|
JsonResult |
Retourne des données au format JSON. |
Json(new { Prop1 = value1, Prop2 = value2 }, JsonRequestBehavior.AllowGet)
|
RedirectResult |
Redirige le client vers une autre URL. |
Redirect("http://www.domaine.fr")
|
RedirectToRouteResult |
Redirige vers une autre action. |
RedirectToRoute("Default", new { controller = "Home", action = "Index" }) RedirectToAction("Index", "Home", new { Prop = value })
|
Action Filters
Permet d’exécuter du code avant ou après que l'action ou le Result soit exécutée.
Type
|
Description
|
OutputCache |
Met en cache la réponse du controller.
|
ValidateInput |
Désactive la validation de la requête.
|
Authorize |
Restreint l'accès au utilisateurs connectés.
|
ValidateAntoForgeryToken |
Prévient les cross site request forgeries.
|
HandleError |
Permet de spécifier une View à utiliser en cas de unhandled exception.
|
Controllers/PersonsController.cs
|
[OutputCache(Duration = 60)]
public class PersonsController : Controller
{
[OutputCache(Duration = 60, VaryByHeader = "X-Requested-With", Location = OutputCacheLocation.Server)]
public ActionResult Index()
{ }
[OutputCache(CacheProfile = "LongCache")]
public ActionResult Index()
{ }
[ChildActionOnly]
[OutputCache(Duration = 60)]
public ActionResult DoSomethingThatTakesTime()
{ }
|
Views/Persons/Index.cshtml
|
@Html.Action("DoSomethingThatTakesTime")
|
Web.config
|
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="LongCache" duration="300" />
<add name="ShortCache" duration="10" />
|
Propriété
|
Description
|
Duration |
durée de mise en cache en secondes
|
VaryByParam |
- * : mise en cache pour chaque combinaison de paramètres
- none : mise en cache sans se préoccuper des paramètres
- name : mise en cache pour chaque valeur du paramètre name
|
Location |
mise en cache du côté serveur, client ou les 2
|
VaryByHeader |
mise en cache pour chaque valeur du paramètre du header spécifié
|
VaryByCustom |
à définir dans Global.asax
|
SqlDependency |
mise en cache jusqu'au changement des données dans la bdd
|
Global filters
App_Start/FilterConfig.cs
|
filters.Add(new HandleErrorAttribute());
|
Custom Filters
Filters/MyCustomFilterAttribute.cs
|
public class MyCustomFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
|
View
Views/Home/Index.cshtml
|
@model FullNamespace.Models.MyModelClass
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div>Message: @Model.SomeProperty</div>
<div>
@* Razor comment *@
Message: @ViewBag.SomeProperty
</div>
@* code block *@
@{
var v = "v";
}
@foreach (var item in Model)
{
<p>Item: @item</p>
@* force Text à ne pas être pris en compte comme code C# mais comme texte HTML *@
@:Text
}
@* opération numérique *@
@(Model.SomeProperty / 10)
|
HTML Helpers
Views/Home/Index.cshtml
|
@* lien *@
@Html.ActionLink("Texte du lien", "Action", new { MyProp = value })
@* appel le DisplayTemplates string (car model.Title est de type string) pour l'affichage de Title *@
@Html.DisplayFor(model => model.Title)
|
Layout View
Définit le layout de toutes les vues.
Views/_ViewStart.cshtml
|
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
|
Section
Définit où est placé la section dans le layout.
Views/Shared/_Layout.cshtml
|
@RenderSection("feature", required: false)
|
Remplit la section avec un contenu.
Views/Home/Index.cshtml
|
@section feature
{
Some text to display.
}
|
Partial View
Permet de découper du code HTML pour le réutiliser ou pour simplifier.
Views/Home/Index.cshtml
|
@Html.PartialView('_MyPartialView', myModel)
|
Views/Home/_MyPartialView.cshtml
|
@model Namespace.Models.MyModel
<div>Some text to display.</div>
|
Depuis un controller
Permet de créer une sous-requête. Utile quand on a pas accès au model, dans _Layout.cshtml par exemple.
Views/Shared/_Layout.cshtml
|
@Html.Action("MyAction", "Home")
|
HomeController.cs
|
[ChildActionOnly]
public ActionResult MyAction()
{
return PartialView("_MyPartialView", myModel);
|
Model
 |
Les attributs de validation influencent aussi Entity Framework. |
 |
La validation se fait du côté client en javascript et aussi du côté serveur. |
|
public class MyClass
{
public int Id { get; set; }
[DisplayName("SuperProperty")]
[DisplayFormat(NullDisplayText = "vide")]
public string MyProperty { get; set; }
[Required]
[StringLength(1024)]
public string MyProperty { get; set; }
[Range(1, 10, ErrorMessage = "Custom error message")]
public int MyProperty { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime Date { get; set; }
[DataType(DataType.Currency)]
public decimal Montant { get; set; }
}
|
Custom Validation
 |
La validation des custum validators se fait uniquement du côté serveur. |
ValidationAttribute
|
[MaxWords(2)]
public string MyProperty { get; set; }
|
MaxWordsAttribute.cs
|
public class MaxWordsAttribute : ValidationAttribute
{
private readonly int _maxWords;
public MaxWordsAttribute(int maxWords)
: base("{0} has too many words.")
{
_maxWords = maxWords;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var valueAsString = value.ToString();
if (valueAsString.Split(' ').Length > _maxWords)
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success;
}
}
|
IValidatableObject
 |
Utile pour valider plusieurs propriétés en même temps. |
|
public class MyClass : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Property1 == "Test" && Property1 == 10)
{
yield return new ValidationResult("Custom error message");
}
}
}
|
|
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
|
Arborescence
- App_Start
- BundleConfig.cs
- FilterConfig.cs
- RouteConfig.cs
- Content
- Controllers
- fonts
- Models
- Scripts
- Views
- Home (pour le controller Home)
- Index.cshtml (pour l'action Index)
- Shared (partagés par tous les controllers)
- Web.config
- Global.asax
- Web.config
Configuration
AppSettings
Web.config
|
<appSettings>
<add key="Key1" value="Value1" />
|
|
string v = ConfigurationManager.AppSettings["Key1"];
|
Ordre de traitement des fichiers de configuration
Config
|
Chemin
|
1. Machine config |
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
|
2. Machine web.config |
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config
|
3. Parent's web.config |
|
4. Your web.config |
|
Les configurations les plus basses écrasent les plus hautes.
Langue et Culture
Web.config
|
<system.web>
<globalization culture="auto" uiCulture="auto" />
<globalization culture="en-US" uiCulture="en-US" />
|
Paquet NuGet
|
Description
|
jQuery.Validation.Localization |
messages de validation
|
jQuery.UI.i18n |
textes pour le datepicker
|
jQuery.Validation.Globalize jquery-globalize cldrjs |
tests de validation
|
JQuery Globalize 0.1.3
 |
jquery-globalize.0.1.3 n'est plus à jour avec jQuery.Validation.Globalize |
Installer jquery-globalize.0.1.3 et jQuery.Validation.Globalize avec NuGet.
App_Start/BundleConfig.cs
|
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate.js",
"~/Scripts/jquery.validate.unobtrusive.js",
// validate globalization
"~/Scripts/jquery.validate.globalize.js"));
bundles.Add(new ScriptBundle("~/bundles/globalize0.1.3").Include(
"~/Scripts/globalize.0.1.3/globalize.js",
"~/Scripts/globalize.0.1.3/cultures/globalize.culture.fr-FR.js"));
));
|
|
@section Scripts {
@Scripts.Render("~/bundles/globalize")
@Scripts.Render("~/bundles/jqueryval")
|
Scripts/jquery.validate.globalize.js
|
(function ($, Globalize) {
Globalize.culture("fr");
$.validator.methods.date = function (value, element) {
var val = Globalize.parseDate(value);
return this.optional(element) || (val instanceof Date);
};
|
JQuery Globalize 1.3.0
 |
cldr-data ne peut pas être installé avec NuGet. |
Installer jQuery.Validation.Globalize avec NuGet. jquery-globalize et cldrjs seront installés comme dépendances.
|
npm install cldr-data
|
App_Start/BundleConfig.cs
|
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate.js",
"~/Scripts/jquery.validate.unobtrusive.js",
// validate globalization
"~/Scripts/jquery.validate.globalize.js"));
bundles.Add(new ScriptBundle("~/bundles/globalize").Include(
"~/Scripts/cldr.js",
"~/Scripts/cldr/*.js",
"~/Scripts/globalize.js",
"~/Scripts/globalize/number.js",
"~/Scripts/globalize/plural.js",
"~/Scripts/globalize/currency.js",
"~/Scripts/globalize/date.js", // date.js doit être en dernier, sinon « validateParameterTypeString is not a function »
"~/Scripts/cldr-data.js" // fichier à créer
));
|
|
@section Scripts {
@Scripts.Render("~/bundles/globalize")
@Scripts.Render("~/bundles/jqueryval")
|
cldr-data.js
|
$.when(
$.getJSON("/Scripts/cldr-data/supplemental/likelySubtags.json"),
$.getJSON("/Scripts/cldr-data/main/fr/numbers.json"),
$.getJSON("/Scripts/cldr-data/supplemental/numberingSystems.json"),
$.getJSON("/Scripts/cldr-data/supplemental/plurals.json"),
$.getJSON("/Scripts/cldr-data/supplemental/ordinals.json"),
$.getJSON("/Scripts/cldr-data/main/fr/currencies.json"),
$.getJSON("/Scripts/cldr-data/supplemental/currencyData.json"),
$.getJSON("/Scripts/cldr-data/main/fr/ca-gregorian.json"),
$.getJSON("/Scripts/cldr-data/main/fr/timeZoneNames.json"),
$.getJSON("/Scripts/cldr-data/supplemental/timeData.json"),
$.getJSON("/Scripts/cldr-data/supplemental/weekData.json"),
).then(function () {
return [].slice.apply(arguments, [0]).map(function (result) {
return result[0];
});
}).then(Globalize.load).then(function () {
Globalize.locale("fr");
});
|
Resources
Log
Health Monitoring
elmah
Installer Elmah.MVC avec Nuget.
Authentication
Web API Authorize
Individual User Accounts |
login + password. Profils utilisateur sont stockés dans une bdd SQL Server. Permet aussi de s'authentifier avec des comptes Facebook, Twitter, Google et autres.
|
Work or School Accounts |
Active Directory, Azure Active Directory, Office 365
|
Windows Authentication |
Active Directory en intranet
|
Individual User Accounts
Controllers/HomeController.cs
|
[Authorize]
public class HomeController : Controller
{
[Authorize]
public ContentResult Secret()
[Authorize(Users = "user1,user2", Roles = "admin")]
public ContentResult Admin()
[AllowAnonymous]
public ContentResult NotASecret()
|
App_Start/Startup.Auth.cs |
configuration
|
Views/Shared/_LoginPartial.cshtml |
Liens Login et Register + message d'accueil
|
Controllers/AccountController.cs |
action login
|
Bdd
Utilise EF pour stocker les profils utilisateurs dans la bdd.
Models\IdentityModels.cs
|
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
|
Web.config
|
<connectionStrings>
<add name="DefaultConnection"
connectionString="Data Source=(LocalDb)\MSSQLLocalDB;
AttachDbFilename=|DataDirectory|\aspnet-[PROJECT NAME]-[DATE].mdf;
Initial Catalog=aspnet-[PROJECT NAME]-[DATE];
Integrated Security=True"
providerName="System.Data.SqlClient" />
|
La bdd localdb se trouve cachée dans le dossier App_Data
|
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
roleManager.Create(new IdentityRole { Name = "admin" });
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
var user = new ApplicationUser { UserName = "john" };
userManager.Create(user, "password");
userManager.AddToRole(user.Id, "admin");
|
External Logins (OpenID, OAuth)
App_Start/Startup.Auth.cs
|
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseMicrosoftAccountAuthentication(
clientId: "",
clientSecret: "");
|
Controllers/AccountController.cs
|
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var result = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);
|
Javascript
Bundle
En release, regroupe et minifie différents fichiers JS dans un seul fichier afin de réduire le nombre de téléchargement.
Global.asax.cs
|
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
BundleConfig.RegisterBundles(BundleTable.Bundles);
|
App_Start/BundleConfig.cs
|
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/custom").Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*",
"~/Scripts/custom.js"));
|
|
@section scripts {
@Scripts.Render("~/bundles/custom")
}
|
AJAX
 |
Installer Microsoft.jQuery.Unobtrusive.Ajax avec NuGet.
Et inclure le script ~/Scripts/jquery.unobtrusive-ajax.js |
Debug
Utiliser IE.
Vider le cache dans IE 11
- Tools → Safety → Delete Browsing History (Ctrl + Shift + Del)
- Tools → Internet Options → Browsing History → Delete browsing history on Exit
CRUD
Create
PersonController.cs
|
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name")] Person person)
{
if (ModelState.IsValid)
{
db.Persons.Add(person);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
|
Views/Person/Create.cshtml
|
@model FullNamespace.Person
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Date, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
|
Read / Detail
PersonController.cs
|
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Person person = db.Persons.Find(id);
if (person == null)
{
return HttpNotFound();
}
return View(person);
}
|
Views/Person/Details.cshtml
|
@model FullNamespace.Person
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
</dl>
|
Update / Edit
PersonController.cs
|
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Person person = db.Persons.Find(id);
if (person == null)
{
return HttpNotFound();
}
return View(person);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Name")] Person person)
{
if (ModelState.IsValid)
{
db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
|
Views/Person/Edit.cshtml
|
@model FullNamespace.Person
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.Id)
<div class="form-group">
@Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
|
Delete
PersonController.cs
|
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Person person = db.Persons.Find(id);
if (person == null)
{
return HttpNotFound();
}
return View(person);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Person person = db.Persons.Find(id);
db.Ecritures.Remove(person);
db.SaveChanges();
return RedirectToAction("Index");
}
|
Views/Person/Delete.cshtml
|
@model FullNamespace.Person
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
</dl>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" />
</div>
}
|
Tests unitaires
Il faut choisir une source de données pour les tests:
- une bdd de test
- des données statique chargées en mémoire, il faudra modifier l'accès aux données via un service
PersonControllerTest.cs
|
[TestClass]
public class PersonControllerTest
{
[TestMethod]
public void Index()
{
var dataService = new InMemoryDataService();
dataService.Add(TestData.Persons);
var controller = new PersonController(dataService);
controller.ControllerContext = new FakeControllerContext();
ViewResult result = controller.Index() as ViewResult;
var model = result.Model as IEnumerable<Person>;
Assert.IsNotNull(result);
Assert.AreEqual(model.Count(), 10);
}
[TestMethod]
public void Create()
{
var dataService = new InMemoryDataService();
var controller = new PersonController(dataService);
controller.Create(new Person());
Assert.AreEqual(1, dataService.Added.Count);
Assert.AreEqual(true, dataService.Saved);
}
[TestMethod]
public void Create_ModelError()
{
var dataService = new InMemoryDataService();
var controller = new PersonController(dataService);
controller.ModelState.AddModelError("", "Invalid");
controller.Create(new Person());
Assert.AreEqual(0, dataService.Added.Count);
}
}
|
PersonController.cs
|
public class PersonsController : Controller
{
IDataService dataService;
public EcrituresController()
{
dataService = new DbDataService();
}
public EcrituresController(IDataService dataService)
{
this.dataService = dataService;
}
|
DataService
IDataService.cs
|
public interface IDataService : IDisposable
{
IQueryable<T> Query<T>() where T : class;
void Add<T>(T entity) where T : class;
void Update<T>(T entity) where T : class;
void Remove<T>(T entity) where T : class;
void SaveChanges();
}
|
DbDataService.cs
|
public class DbDataService : DbContext, IDataService
{
public DbSet<Person> Persons { get; set; }
public void Add<T>(T entity) where T : class
{
Set<T>().Add(entity);
}
public IQueryable<T> Query<T>() where T : class
{
return Set<T>();
}
public void Remove<T>(T entity) where T : class
{
Set<T>().Remove(entity);
}
public void Update<T>(T entity) where T : class
{
Entry(entity).State = EntityState.Modified;
}
void IDataService.SaveChanges()
{
SaveChanges();
}
}
|
InMemoryDataService.cs
|
class InMemoryDataService : IDataService
{
public Dictionary<Type, object> Sets = new Dictionary<Type, object>();
public List<object> Added = new List<object>();
public List<object> Updated = new List<object>();
public List<object> Removed = new List<object>();
public bool Saved;
public IQueryable<T> Query<T>() where T : class
{
return Sets[typeof(T)] as IQueryable<T>;
}
public void Add<T>(IQueryable<T> objects)
{
Sets.Add(typeof(T), objects);
}
public void Dispose()
{
}
public void Add<T>(T entity) where T : class
{
Added.Add(entity);
}
public void Update<T>(T entity) where T : class
{
Updated.Add(entity);
}
public void Remove<T>(T entity) where T : class
{
Removed.Add(entity);
}
public void SaveChanges()
{
Saved = true;
}
}
|
TestData.cs
|
class TestData
{
public static IQueryable<Person> Persons
{
get
{
var persons = new List<Person>();
return persons.AsQueryable();
|
FakeControllerContext
FakeControllerContext.cs
|
class FakeControllerContext : ControllerContext
{
HttpContextBase _context = new FakeHttpContext();
public override HttpContextBase HttpContext
{
get => _context;
set => _context = value;
}
}
class FakeHttpContext : HttpContextBase
{
HttpRequestBase _request = new FakeHttpRequest();
public override HttpRequestBase Request => _request;
}
class FakeHttpRequest : HttpRequestBase
{
public override string this[string key] => null;
public override NameValueCollection Headers => new NameValueCollection();
}
|
Exemples
Filtre de recherche
Controllers/PersonController.cs
|
public ActionResult Index(string searchTerm = null)
{
var persons = db.Persons.Include(p => p.Company)
.Where(e => searchTerm == null || p.Name.Contains(searchTerm))
.OrderBy(p => p.BirthDate)
.Take(20);
return View(persons.ToList());
}
|
Views/Persons/Index.cshtml
|
@model IEnumerable<Namespace.Models.Person>
<form method="get">
<input type="search" name="searchTerm" />
<input type="submit" value="Search" />
</form>
|
Filtre de recherche avec AJAX
Views/Persons/Index.cshtml
|
@using (Ajax.BeginForm(new AjaxOptions
{
HttpMethod = "get",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "personsList"
}))
{
<input type="search" name="searchTerm" />
<input type="submit" value="Search" />
}
@Html.Partial("_Persons", Model)
|
Views/Persons/_Persons.cshtml
|
<div id="personsList">
|
Controllers/PersonsController.cs
|
if (Request.IsAjaxRequest())
{
return PartialView("_Persons", persons);
}
return View(persons);
|
PagedList
Installation: NuGet → PagedList.Mvc
Controllers/PersonsController.cs
|
public ActionResult Index(int page = 1)
{
var persons = db.Persons.ToPagedList(page, 10);
return View(persons);
}
|
Views/Persons/Index.cshtml
|
@Html.Partial("_Persons", Model)
|
Views/Persons/_Persons.cshtml
|
@using PagedList
@using PagedList.Mvc
@model IEnumerable<Namespace.Models.Person>
<div id="personsList">
<div class="pagedList" data-target="#personsList">
@Html.PagedListPager((IPagedList)Model, page => Url.Action("Index", new { page }), PagedListRenderOptions.MinimalWithItemCountText)
</div>
<table class="table">
|
PagedList avec AJAX
|
$(function () {
var getPage = function () {
var $a = $(this);
var options = {
url: $a.attr("href"),
data: $("form").serialize(),
type: "get"
};
$.ajax(options).done(function (data) {
var target = $a.parents("div.pagedList").attr("data-target");
$(target).replaceWith(data);
});
return false;
};
$(".body-content").on("click", ".pagedList a", getPage);
});
|
Installer avec NuGet: jQuery.UI.Combined, jQuery.UI.i18n
Scripts/jquery-ui-custom.js
|
if (!Modernizr.inputtypes.date) {
$(function () {
$("input[type = date]").datepicker($.extend(
{},
$.datepicker.regional['fr'],
{
showAnim: 'drop'
}
));
});
}
|
App_Start/BundleConfig.cs
|
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate.js",
"~/Scripts/jquery-ui-1.12.1.js",
"~/Scripts/jquery-ui-i18n.js",
"~/Scripts/jquery-ui-custom.js"));
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/site.css",
"~/Content/themes/base/all.css"));
|
JQuery UI Datepicker