.NET Core, ASP.NET Core, and ASP.NET Core MVCsddconf.com/brands/sdd/library/aspnetcoremvc.pdf ·...
Transcript of .NET Core, ASP.NET Core, and ASP.NET Core MVCsddconf.com/brands/sdd/library/aspnetcoremvc.pdf ·...
.NETCore,ASP.NETCore,andASP.NETCoreMVC
bravenewworld
Outline
• Motivation• .NETCore• ASP.NETCore• ASP.NETCoreMVC
Motivation
• .NEThasastronghistory• Verypopular• Lotsofinvestments
• ThereismorethanjustWindows• Manymoreplatforms,devices,andclouds
• .NETisevolving• .NETneedstolearntoruninmoreplaces• .NETneedsmoderntooling
.NETruntimestargetaplatform
• .NETFramework• Windows-only
• .NETCore• Cross-platformruntime
• .NETNative(UWP)• Mono• WindowsPhone• More…
.NETCore:nextgen.NETforserverapps
• Crossplatform• Windows,Linux,Mac,FreeBSD
• Portable• Canbe~/bindeployed• Canbeuserormachineinstalledaswell
• Opensource• https://github.com/dotnet/coreclr• Containscoreruntimeandmscorlib (e.g.GC,JIT,BCL)• Doesnotcontainmanyframeworks(e.g.WCF,WPF)
Developmentecosystem
• SDK• Command-linetooling(dotnet)
• Projectsystem• File-systembasedprojectsystem(project.json)
• Runtime,libraries,andpackaging• NuGet-focused
• Editors/IDEs• Anytexteditor(VSCode,Emacs,Sublime,etc)andOmniSharp (OSS)• VisualStudio(Microsoft)• ProjectRider(JetBrains)
Installing.NETSDK
• Usenightlybuilds(untilRC2isreleased)• https://github.com/dotnet/cli
dotnet :commandlinetool
• Createnewproject• InstallNuGetdependencies• Buildapplication• Load.NETandrunapplication• Packagelibrary• Publishapplication
dotnet new
• Createsnewproject• program.cs• project.json
• Console-basedapplicationusing System;
namespace ConsoleApplication{
public class Program{
public static void Main(string[] args){
Console.WriteLine("Hello World!");}
}}
project.json
• Projecttype• Applicationorlibrary
• Dependencies• PrimarilyfromNuGet
• Targetframework(s)• Targetframeworkmoniker(TFM)
{"version": "1.0.0-*","buildOptions": {"emitEntryPoint": true
},"dependencies": {"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-*"
},"frameworks": {"net46" : {},"netcoreapp1.0": {"dependencies": {
"Microsoft.NETCore.App": {"type": "platform","version": "1.0.0-rc2-3002659"
}}
}}
}
.NETplatformstandard
• Identifier(TFM)forrequiredframework• ReplacementforPCLplatformversioningnightmare
• LibrariestargetanexpectedAPIfromframework• "netstandard1.0","netstandard1.1",…,"netstandard1.5"• Canuselibrariesfromearlier.NETStandardversion
• Applicationstargetaspecificplatform(andthusframework)• "net451","net452","net46","net461","netcoreapp1.0",etc…• Platformssupportaspecific.NETStandardversion
https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md
Platformsupportfor.NETStandard
dotnet restore
• DownloadsNuGet dependencies• Mightneedalocalnuget.config totargetnightlybuildsfrommyget.org
• Buildsproject.json.lock• Snapshotofdependencyversions• Neededtoloadapplication
<?xml version="1.0" encoding="utf-8"?><configuration><packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below --><clear /><add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" /><add key="AspNetCI" value="https://www.myget.org/F/aspnetcirelease/api/v3/index.json" />
</packageSources></configuration>
dotnet build/dotnet run/dotnet app.dll
• Buildsproject,orbuildsandrunsapplication• -cindicatesconfiguration(release/debug)• -findicatesframeworktotarget• -vemitsverboselogoutput
• Binariesoutputto~/bin/<configuration>/<framework> folder
ASP.NETCore
• Thenew pipeline• Middleware• Dependency Injection• Configuration
Motivation
• Modernwebstack• Modernpackagesystem(NuGet)• Lightweight/composable runtime• Dependencyinjection• Flexibleconfiguration/deployment
"EmptyWebApplication"
OWINMiddlewareArchitecture
• Middlewarearelinkedcomponentsthatprocessrequests• Applicationcodetargetingaframework
Host
OWINServer
SomeMiddleware
SomeOtherMiddlewareUserAgent Application
ASP.NETCore
• ASP.NETCoreisHTTPpipelineimplementation• sitsontopof.NETCore• usesthemiddlewareconcept(butatahigherabstractionlevelthanOWIN)• comeswithitsownserver(Kestrel)• addsDItoprovideservices
• ASP.NETCoreMVCisMicrosoft'sapplicationframework
Host
.NETCore
ASP.NETCore
Middleware MiddlewareUserAgent MVC
DI
How ASP.NETCoreApplications start
dotnet run
Application.dll
ASP.NETCoreModuleIIS
Startup.cs
ConfigureServices(…)
Configure(…)
startprocess
LoadingASP.NETCorepublic class Program{
public static void Main(){
var host = new WebHostBuilder().UseKestrel().UseIISIntegration().UseStartup<Startup>().Build();
host.Run();}
}
public class Startup{
public void Configure(IApplicationBuilder app){
...}
}
PipelineprimitivesStart
app.Use(context,next)
app.Map("/path")
app.Use(context,next)
app.Run(context)
app.Use(context,next)
app.Run(context)
Run
app.Run(async context =>{
await context.Response.WriteAsync("Hello ASP.NET Core");});
namespace Microsoft.AspNetCore.Builder{
public delegate Task RequestDelegate(HttpContext context);}
Map
app.Map("/hello", helloApp =>{
helloApp.Run(async (HttpContext context) =>{
await context.Response.WriteAsync("Hello ASP.NET Core");});
});
Use
app.Use(async (context, next) =>{
if (!context.Request.Path.Value.EndsWith("/favicon.ico")){
Console.WriteLine("pre");Console.WriteLine(context.Request.Path);
await next();
Console.WriteLine("post");Console.WriteLine(context.Response.StatusCode);
}else{
await next();}
});
Middlewareclasses
public class InspectionMiddleware{
private readonly RequestDelegate _next;
public InspectionMiddleware(RequestDelegate next){
_next = next;}
public async Task Invoke(HttpContext context){
Console.WriteLine($"request: {context.Request.Path}");await _next(context);
}}
app.UseMiddleware<InspectionMiddleware>();
Dependency Injection
• Various places• Configure• Middlewareclasses• Higher-levelframeworks (e.g.MVCcontroller)
• Hostprovided dependencies (e.g.IHostingEnvironment,ILoggerFactory)• Dependencies set up inConfigureServices
DIExamples
public class Startup{
public Startup(IHostingEnvironment environment){ /* stuff */ }
public void ConfigureServices(IServiceCollection services){ /* register more stuff */ }
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory){
/* add middleware */}
}
Registering dependencies
• Newinstance "percall"
• Newinstance perHTTPrequest
• Singleton
services.AddTransient<IMyCustomService, MyCustomService>();
services.AddSingleton<IMyCustomService, MyCustomService>();
services.AddScoped<IMyCustomService, MyCustomService>();
Configuration
• web.config is no more
• Newconfiguration system based onkey/value pairs• command line• environmentvariables• JSONfiles• INIfiles
• Configuration can come frommultiplesources• lastsource wins
Example
public class Startup{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env){
Configuration = new ConfigurationBuilder().SetBasePath(env.ContentRootPath).AddJsonFile("config.json").AddJsonFile($"config.{env.EnvironmentName}.json", optional: true).AddEnvironmentVariables().Build();
}
// more}
Usingconfigurationpublic class Startup{
IConfiguration _configuration;
public Startup(){
_configuration = new ConfigurationBuilder()....Build();
}
public void Configure(IApplicationBuilder app){
var copyright = new Copyright{
Company = _configuration.Get("copyright_company"),Year = _configuration.Get("copyright_year")
};
app.Run(async (context) =>{
await context.Response.WriteAsync($"Copyright {copyright.Year}, {copyright.Company}");});
}}
{"copyright": {
"year": "2015","company": "Foo Industries"
}}
ASP.NETCoreMVC
• Packaging• Middleware• Routingandactionselection• Controllerinitialization• Modelbindingchanges• Razor• Filters• APIs• Errorhandling
Packaging
• MVCispackagedentirelyasaNuGet• Microsoft.AspNetCore.Mvc
{"dependencies": {
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-*","Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-*"
}}
Middleware
• MVCisconfiguredasmiddleware• InConfigureServices viaAddMvc• InConfigureviaUseMvc
public class Startup{
public void ConfigureServices(IServiceCollection services){
services.AddMvc();}
public void Configure(IApplicationBuilder app){
app.UseMvc();}
}
Overridingdefaultsettings
• Delegatecallbackparam usedtooverridedefaults• AlsofluentAPIonresultfromAddMvc()
public class Startup{
public void ConfigureServices(IServiceCollection services){
services.AddMvc(mvc =>{
mvc.Filters.Add(...);mvc.ViewEngines.Add(...);mvc.InputFormatters.Add(...);mvc.OutputFormatters.Add(...);
});}
}
Routing
• RoutesconfiguredviaUseMvc• RouteParameters.Optional fromMVC5removed
public void Configure(IApplicationBuilder app){
app.UseMvc(routes =>{
routes.MapRoute("old_default","{controller}/{action}",new {
controller = "Home", action="Index"});
routes.MapRoute("new_default","{controller=Home}/{action=Index}/{id?}");
});}
Controllers
• Controllerbaseclassstillprovided• ActionresultsnowimplementIActionResult• Controllerbaseprovidesmanyhelperstocreateactionresults
• View(),PartialView(),Content(),Json(),Ok(),Created(),HttpNotFound(),HttpUnauthorized(),HttpBadRequest(),File(),PhysicalFile(),Redirect(),RedirectPermanent()
public class HomeController : Controller{
public IActionResult Index(){
return View();}
}
Attributerouting
• Attributeroutingenabledbydefault
public class HomeController : Controller{
// ~/ or ~/hello-world[Route("/")][Route("/hello-world")]public IActionResult Index(){
return View();}
}
Attributerouting
• Attributeroutingcanbeappliedtoclass• [controller]and[action]actastokens
[Route("[controller]/[action]")]public class HomeController : Controller{
// ~/Home/Indexpublic IActionResult Index(){
return View();}
}
CombiningRouteattributes
• Routeattributesinheritpath• RoutePrefix fromMVC5removed
• Canreplaceinheritedpath• Iftemplatestartswith"/"or"~/"
[Route("[controller]")]public class HomeController : Controller{
// ~/Home/hello[Route("hello")]public IActionResult Index(){
return View();}
// ~/hello[Route("/hello")]public IActionResult Index2(){
return View();}
}
Routeparameters
• [Route]allowsparameters• With{param}syntax
• Supportsfilters• With{param:filter}syntax
[Route("[controller]/[action]")]public class HomeController : Controller{
// GET ~/Home/Indexpublic IActionResult Index(){
return View();}
// GET ~/Home/Index/5[Route("{id:int}")]public IActionResult Index(int id){
return View();}
}
HTTPmethodbasedroutes
• HttpGet,HttpPost,HttpPut,HttpDelete,HttpPatch• Filteractionmethodonrequestmethod• Buildon[Route]semantics
[Route("[controller]/[action]")]public class HomeController : Controller{
// GET ~/Home/Index[HttpGet]public IActionResult Index(){
return View();}
// ~/Submit[HttpPost("/Submit")]public IActionResult Submit(){
return View();}
}
Areas
• Areasdefinedwiththe[Area]attribute• Usedtomatchan{area}routeparam• Attributeroutingallows[area]routetoken
• Viewsmuststillresideunder~/Areas/<area>/Views/<controller>
public void Configure(IApplicationBuilder app){
app.UseMvc(routes =>{
routes.MapRoute("new_default","{area}/{controller=Home}/{action=Index}/{id?}");
});}
[Area("account")]public class HomeController : Controller{
// ...}
POCOcontrollers
• ControllerclassescanbePOCO• DiscoveredinprojectsthatreferenceMicrosoft.AspNetCore.Mvc.*• Identifiedby"Controller"classnamesuffix• [NonController]disables
Dependencyinjection
• Caninjectdependenciesintocontrollerctor• Specialper-requesttypescanbepropertyinjectedwithdecoratorattribute• ActionContext• ControllerContext
public class HomeController{
IHttpContextAccessor _accessor;
public HomeController(IHttpContextAccessor accessor){
_accessor = accessor;}
[ControllerContext]public ControllerContext ControllerContext { get; set; }
// ...}
Razor
• Sharedconfig• _ViewStart and_ViewImports
• Chunks• @directives
• TagHelpers• LikeWebForms customcontrols
• ViewComponents• Childactionreplacements
Sharedrazorconfiguration
• _ViewStart.cshtml stillexists• Cannoweasilybeputinapplicationroot• Layoutassignmentnolongerisfullpath
• _ViewImports.cshtml isnew• Allowsforsharing@using,@addTagHelperchunksacrossviews• Canbelocatedinthesameplacesas_ViewStart.cshtml
Razordirectives(akachunks)
• @model,@using,@section,@functionsstillexist• @helperisgone• @inject,@addTagHelper arenew
• Also,@awaitHtml.PartialAsync() isnew
@inject
• Allowsdependencyinjectionintoview• @inject<type><property>
@using Microsoft.Framework.OptionsModel@inject IOptions<MyConfig> Config
<h2>@Config.Options.SiteName</h2>
Taghelpers
• LikecustomcontrolsforMVC• Allowserver-sidecodetoinspecttheelement• Canmodifyattributes,tag,and/orcontents
• @addTagHelper Namespace.ClassName,Assembly• Or@addTagHelper *,Assembly
@addTagHelper SpanTagHelper, YourProjectName
<span emoji="smile" />
Taghelperimplementation
• TagHelper baseclass• Classnameusedtomatchelement
• OverrideProcessorProcessAsync• InspectelementviaTagHelperContext• AlteroutputviaTagHelperOutput
public class SpanTagHelper : TagHelper{
public override void Process(TagHelperContext context, TagHelperOutput output)
{if (context.AllAttributes.ContainsKey("emoji") &&
"smile" == context.AllAttributes["emoji"].ToString()){
output.Attributes.Add("title", "smile");output.Content.SetContent(" :) ");output.SelfClosing = false;
}}
}
Taghelperimplementation
• [TargetElement]canbeusedtomatchelement• Attributescanbeusedtofilter
• [HtmlAttributeName]willreadincomingattribute• Willremovefromoutput
[TargetElement("span", Attributes = "emoji")]public class EmojiTagHelper : TagHelper{
[HtmlAttributeName("emoji")]public string Emoji { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{if ("smile" == Emoji){
output.Attributes.Add("title", "smile");output.Content.SetContent(" :) ");output.SelfClosing = false;
}}
}
MVCtaghelpers<a asp-controller="Manage" asp-action="Index">Manage Your Account</a>
<form asp-controller="Account" asp-action="LogOff" method="post"></form>
<environment names="Staging,Production"><h1>You're in production!</h1>
</environment>
<link rel="stylesheet"href="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/css/bootstrap.min.css"asp-fallback-href="~/lib/bootstrap/css/bootstrap.min.css"asp-fallback-test-class="hidden" asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" />
<script src="//ajax.aspnetcdn.com/ajax/jquery.validation/1.11.1/jquery.validate.min.js"asp-fallback-src="~/lib/jquery-validation/jquery.validate.js"asp-fallback-test="window.jquery && window.jquery.validator">
</script>
Validationtaghelpers
<form asp-controller="Account" asp-action="ForgotPassword" method="post><h4>Enter your email.</h4>
<div asp-validation-summary="ValidationSummary.All" class="text-danger"></div>
<div><label asp-for="Email"></label><input asp-for="Email" class="form-control" /><span asp-validation-for="Email" class="text-danger"></span>
</div></form>
Viewcomponents
• Replacementforchildactions• Partialviewsstillexist
• Allowforapartialviewthatrunscontroller-likecode• Supportsdependencyinjection
@Component.Invoke("Menu", 3)
Or
@await Component.InvokeAsync("Menu", 3)
Viewcomponents
• ViewComponent baseclass• Matchedbyclassprefix• Orcanuse[ViewComponent]onPOCO
• ImplementInvokeorInvokeAsync• ReturnsIViewComponentResultorTask<IViewComponentResult>
public class MenuViewComponent : ViewComponent{
ICustomMenuService _menu;
public MenuViewComponent(ICustomMenuService menu){
_menu = menu;}
public IViewComponentResult Invoke(int depth){
var menuModel = _menu.GetMenu(depth);return View("Index", menuModel);
}}
Viewcomponents
• Viewcomponentviewsareunder:• ~/Views/<controller>/Components/<component>• ~/Views/Shared/Components/<component>
Filters
• Dependencyinjection• Resourcefilter• Async support
TypeFilter
• Allowsforfiltersthatrequiredependencyinjection• ImplementedviaIFilterFactory
public class MyActionFilter : Attribute, IActionFilter{
private IHostingEnvironment _env;
public MyActionFilter(IHostingEnvironment env){
_env = env;}
// ...}
[TypeFilter(typeof(MyFilter))]public IActionResult Index(){
// ...}
IResourceFilter
• Surroundsmodelbinding,action,andresult(includingthosefilters)
• ResourceExecutingContext• Valueproviders,modelbinders,inputformatters,validationproviders• Canaltertheseoneachrequest
public interface IResourceFilter : IFilter{
void OnResourceExecuting(ResourceExecutingContext context);void OnResourceExecuted(ResourceExecutedContext context);
}
Async filters
• AllfiltersnowhaveIAsync<Filter> support• Authorization,Resource,Action,Result,Exception• Patternsimilartomiddlewarepipeline
public class MyResourceFilter : Attribute, IAsyncResourceFilter{
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{// prevar resourceExecutedContext = await next();// post
}}
WebAPI
• Formatters• Contentnegotiation• Formatfilters• XMLsupport
Formatters
• Formattershavebeensplitintotwogroups• Inputformatterstriggeredvia[FromBody]• OutputformatterstriggeredviaObjectResult
Inputformatters
• InputFormatter baseclassprovidesstartingpoint• SupportedMediaTypespropertyusedtomatchContent-Typeheader• [Consumes]filtercanbeusedtolimitformatters
Formatter Contenttype Comment
StringInputFormatter text/plain
JsonInputFormatter application/json, text/json
XmlSerializerInputFormatter application/xml, text/xml Notregisteredbydefault
XmlDataContractSerializerInputFormatter application/xml, text/xml Notregisteredbydefault
Outputformatters
• ObjectResult choosesformatterfromAcceptheader• OutputFormatter baseclasshasSupportedMediaTypesproperty• ContentTypespropertyor[Produces]filtercanbesetexplicitlytolimitformatters• IfAcceptcontains"*/*"thenrestofAcceptvaluesignored
• RespectBrowserAcceptHeader onMvcOptions canchangebehavior
Formatter Accepttype Comment
StringOutputFormatter text/plain
JsonOutputFormatter application/json, text/json
XmlSerializerOutputFormatter application/xml, text/xml Notregisteredbydefault
XmlDataContractSerializerOutputFormatter application/xml, text/xml Notregisteredbydefault
FormatFilter &FormatFilterAttribute
• AllowsoverridingofAcceptheader• Looksfor"format"routeorqueryparam• FormatterMappings onMvcOptions indicatesformattomediatypemapping
• SetsContentTypesonObjectResultpublic void Configure(IApplicationBuilder app){
app.UseMvc(routes =>{
routes.MapRoute("default","api/{controller} ");
routes.MapRoute("formatted","api/{controller}.{format}");
});}
[Produces]&[Consumes]attributes
• Usedtocontrolwhatformattersusedonrequest/response
[HttpGet][Consumes("application/xml")] [Produces("application/json"")] public object Get(){
return new {...};}
XMLcompatibilityshim
• LibrarytohelpmigratefromWebAPItoCoreMVC• Microsoft.AspNetCore.Mvc.WebApiCompatShim
• Providesoldclassesthatmaptothenewframework• ApiController• FromUriAttribute• HttpRequestMessage helpers/extensions/modelbinder• HttpResponseMessage helpers/extensions/formatter• HttpResponseException• HttpError
Errorhandling
• HandleError fromMVC5hasbeenremoved• Resourcefilter'spostprocessingrunsafterexceptionfilters• Lastchanceplaceto"handle"exceptionswitharesult• Orjustcanlogexceptions
Errorpages
• Diagnosticsmiddleware• Microsoft.AspNetCore.Diagnostics• UseDeveloperExceptionPage usefulfordevelopment/debuggingerrorinfo• UseExceptionHandler usefulforproductionerrorpages
• Logserrorinformation• Invokeserrorpath
Summary
• Bravenew(yetsomewhatfamiliar)world• .NETCoreisacross-platformframework• ASP.NETCoreis aflexibleHTTPpipeline architecture• MVCandWebAPIhavehadaquiteamakeover