Razor Pages #494

Closed
NTaylorMullen opened this Issue May 10, 2014 · 66 comments

Comments

@NTaylorMullen
Member

NTaylorMullen commented May 10, 2014

We will support a simple model where a user can crate a CSHTML page that can handle requests but still use the primitives of MVC.

| 57 | 10153 | Reported By: Yishai Galatzer |

@JaneZhouQ JaneZhouQ added this to the Post Alpha milestone May 13, 2014

@kanchanm kanchanm modified the milestones: Alpha, Post Alpha May 14, 2014

@danroth27 danroth27 changed the title from We will support Web Pages style pages, using WebFX as the underlying framework. to Web Pages framework May 29, 2014

@danroth27 danroth27 modified the milestones: 1.0.0-beta1, 1.0.0-alpha2 May 29, 2014

@danroth27 danroth27 added the feature label May 29, 2014

@danroth27 danroth27 modified the milestones: 6.0.0-rc1, Backlog Sep 22, 2014

@danroth27 danroth27 added 0 - Backlog and removed 1 - Ready labels Sep 22, 2014

@danroth27 danroth27 removed the 0 - Backlog label Oct 30, 2014

@yishaigalatzer yishaigalatzer modified the milestones: Backlog, 6.0.0-beta3 Jan 13, 2015

@danroth27 danroth27 added enhancement and removed enhancement labels Jul 7, 2016

@DamianEdwards DamianEdwards modified the milestones: 1.1.0, Backlog Jul 13, 2016

@DamianEdwards

This comment has been minimized.

Show comment
Hide comment
@DamianEdwards

DamianEdwards Jul 14, 2016

Member

Goals

We are trying to:

  • Make dynamic HTML and forms with ASP.NET Core easier, e.g. how many files & concepts required to print Hello World in a page, build a CRUD form, etc.
  • Reduce the number of files and folder-structure required for page-focused MVC scenarios
  • Simplify the code required to implement common page-focused patterns, e.g. dynamic pages, CRUD forms, PRG, etc.
  • Enable the ability to return non-HTML responses when necessary, e.g. 404s
  • Use and expose the existing MVC primitives as much as possible, e.g.:
    • Model binding
    • Model validation
    • Model meta-data
    • Action filters (the ones that make sense)
    • Action results
    • HTML Helpers
    • Tag Helpers
    • Existing Razor directives, e.g. @Inject
    • Layouts & partials
    • _ViewStart.cshtml & _ViewImports.cshtml (should we support _PageStart.cshtml & _PageImports.cshtml instead/as well?)
  • Allow straightforward migration to traditional MVC structure
  • Do this at a reasonable cost, e.g. 1-2 developers during 1.1 release

Non-goals

We are not trying to:

  • Create a scripted page framework to compete with PHP, etc.
  • Hide C# with a DSL in Razor or otherwise
  • Create new primitives that are only applicable here (i.e. they should be usable in traditionally structured MVC apps too)
  • Create undue burdens for us with regards to tooling support, etc.

Enabling

Packaging

The "MVC View Pages" feature will ship in a standalone package, e.g. Microsoft.AspNetCore.Mvc.Razor.ViewPages, that will be referenced by the MVC meta-package, Microsoft.AspNetCore.Mvc. Users of the lower-level Microsoft.AspNetCore.Mvc.Core package can bring in the MVC Pages package to enable it without using the meta-package.

Startup

The IServiceCollection.AddMvc() and IApplicationBuilder.UseMvc() extension methods will enable MVC Pages by default. There will be appropriate extension methods to enable MVC Pages explicitly in the cases where these extensions methods are not used, e.g. when using IServiceCollection.AddMvcCore().

@page directive

MVC pages are represented by CSHTML files that use the new @page directive. This directive instructs the MVC Razor host to treat the CSHTML file as a standalone page for the purposes of request handling.

e.g.

@page
Hello, World! It's @DateTime.Now on the server

@Class block directive

MVC pages can add members to the generated class using the new @class block directive. This directive is effectively an alias of the existing @functions directive. All members added to the class are available to the page during request processing.

Page Actions

Public methods added to the page class in the @class block are candidates for handling request processing, much like action methods on MVC controllers. Methods that match the selection rules become Page Actions, and one (or none) will be selected as the handling Page Action for any given request to the page.

Action Selection

Action selection is based on simple HTTP verb name matching, with support for the prefix On, e.g.: GET requests will match methods called Get or OnGet, POST requests will match methods called Post or OnPost.

Situations that result in more than one action matching are an error should throw an exception (like an ambiguous action selection error in a controller).

If no actions are matched, the page will simply render.

Action Results

Page Actions must return an IActionResult. Typically this will be a ViewResult that renders the current page, e.g. return View();, but any IActionResult is supported. This allows pages to return non-HTML results, e.g. 404 not found, or even JSON or other formats if so desired.

e.g.:

@page
@class {
    public IActionResult OnGet()
    {
        return View();
    }
}
Hello, World!

Parameter Binding

Parameters of Page Actions will be populated using model-binding, just like action methods are in MVC controllers. As such, it will support binding of simple and complex types from the registered value providers, e.g. query string, form, cookie, route data, etc., and model-binders.

e.g.:

@page
@class {
    public IActionResult OnGet(int? id)
    {
        if (id.HasValue && id < 0)
        {
            return NotFound();
        }
        return View();
    }
}
Found!

Properties & Helper Methods

Members in the @class block are available to the page during execution of the page action and during rendering, making it easy to pass data between the two stages.

e.g.

@page
@class {
    public IActionResult OnGet(int? id)
    {
        if (id.HasValue && id < 0)
        {
            return NotFound();
        }
        Id = id;
        return View();
    }

    public int? Id { get; set; }
}
Id = @Id

@model directive

The @model directive can be used to declare the model type for the page. It will generate a Model property on the page class of the declared type, as well as using the model type as the generic type argument for the IHtmlHelper<T> instance available to the page.

@page
@model Customer
@class {
    public IActionResult OnGet(int? id)
    {
        if (id.HasValue && id < 0)
        {
            return NotFound();
        }
        var model = new Customer { Id = id, FirstName = "Jane", LastName = "Smith" };
        return View(model);
    }
}
Customer: @Model.FirstName @Model.LastName

Inline View Models using @model & @Class

Unlike on views, the @model directive will not change the base type of the generated page class to a generic variant. This is to allow the page the ability to declare nested classes in its @class block and use those as the model type with @model.

e.g.

@page
@model ViewModel
@class {
    public IActionResult OnGet(int? id)
    {
        return id.HasValue && id < 0
            ? NotFound()
            : View(new ViewModel {
                Customer = new Customer {
                    Id = id,
                    FirstName = "Jane",
                    LastName = "Smith"
                }
        });
    }

    public class ViewModel
    {
        public Customer Customer { get; set; }
    }
}
Customer: @Model.Customer.FirstName @Model.Customer.LastName

Code-behind class

IDEA: Class of same-name as page file will be automatically used as base type, not sure if this is a good idea, could be hard to make work in expected way reliably.

@inherits directive

The @inherits directive can be used to change the type that the generated page class will derive from. The type specified can be a POCO, or derive from the page base type in MVC (TBD). This way, code can be separated from the CSHTML file and placed in a CS file that it is compiled with the application (rather than as part of the CSHTML compilation process, either at runtime or as part of view pre-compilation).

In the case the base type is a POCO, the existing contextualizing attributes can be placed on properties of the appropriate types to gain access to relevant context objects from MVC, i.e. they will be set to the current instances during page activation, e.g. [HttpContext]. (We might possibly end up with some new context types, e.g. PageContext, in order to make the members appropriate for pages)

e.g.

public class MyPage : Page
{
    public IActionResult OnGet(int? id)
    {
        Id = id;
        return View();
    }

    public int? Id { get; set; }
}
@page
@inherits MyPage
Id was @Id

@Inject directive

Yeah, you can use the @inject directive to generate properties that have instances injected from the DI container, just like in views., e.g. @inject AppDbContext Db

@{ } code blocks

They work just like they do in views and run as part of page rendering.

Page URLs

The path to the page file from the configured pages root path (defaults to IHostingEnvironment.ContentRootPath), including the file name, but excluding the file extension, becomes the URL that the page will be served in response to. Pages with a file name of "Index.cshtml" will also act as the default page for the sub-path (folder) they are in, e.g.:

Page Path URL
/Foo.cshtml /foo
/Foo/Bar.cshtml /foo/bar
/Index.cshtml /index
/Index.cshtml /
/Foo/Index.cshtml /foo/

Explicit routing using conventional routes

TODO: routeBuilder.MapPageRoute("customers/{id}", "Customer.cshtml")

Explicit routing using attribute routes

TODO: Using attribute routes on the base class when using @inherits

Anti-forgery (CSRF protection)

TODO

Examples

Hello World

Project Structure

Index.cshtml
Program.cs
project.json

Program.cs

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace HelloWorld
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .SetContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }

        public class Startup
        {
            public void ConfigureServices(IServiceCollection services) => services.AddMvcPages();
            public void Configure(IApplicationBuilder app) => app.UseMvcPages();
        }
    }
}

Index.cshtml

@page
Hello, World! It's @DateTime.Now

Single-page CRUD

Project Structure

Data/
    AppDbContext.cs
    Customer.cs
wwwroot/
    [static files]
_Layout.cshtml
_ValidationScripts.cshtml
appsettings.json
Customers.cshtml
Program.cs
project.json
Startup.cs

Program.cs

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace HelloWorld
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .SetContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }

        public class Startup
        {
            public Startup(IHostingEnvironment env)
            {
                var builder = new ConfigurationBuilder()
                    .SetBasePath(env.ContentRootPath)
                    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
                Configuration = builder.Build();
            }

            public IConfigurationRoot Configuration { get; }

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddDbContext<AppDbContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

                services.AddMvcPages();
            }

            public void Configure(IApplicationBuilder app)
            {
                app.UseStaticFiles();
                app.UseMvcPages();
            }
        }
    }
}

Data/Customer.cs

namespace MyApp.Data
{
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string DateTimeOffset BirthDate { get; set; }
        public string FavoriteDrink { get; set; }
    }
}

Data/AppDbContext.cs

namespace MyApp.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {

        }

        public DbSet<Customer> Customers { get; set; }
    }
}

Customers.cshtml

@page
@using MyApp.Data
@model ViewModel
@inject AppDbContext Db
@class {
    public IActionResult OnGet(int? id)
    {
        var customer = id.HasValue ? await Db.Customers.SingleOrDefaultAsync(c => c.Id == id) : (Customer)null;
        return id.HasValue && customer == null ?
            NotFound() :
            View(new ViewModel {
                Customer = customer,
                Title = customer != null ? $"Edit Customer {customer?.Id}" : "New Customer"
            });
    }

    public IActionResult OnPost(ViewModel model)
    {
        if (!ModelState.IsValid())
        {
            // Model errors, just return the view to show them the errors
            model.Title = "Edit Customer";
            return View(model);
        }

        if (!model.Customer.Id.HasValue)
        {
            // Create
            Db.Customers.Add(model.Customer);
            ViewModel.AlertMessage = $"New customer {model.Customer.Id} created successfully!";
        }
        else
        {
            // Update
            Db.Attach(model.Customer, EntityState.Changed);
            ViewModel.AlertMessage = $"Customer {model.Customer.Id} updated successfully!";
        }

        await Db.SaveChangesAsync();

        return Reload(new { id = model.Customer.Id });
    }

    public class ViewModel
    {
        public string Title { get; set; }
        public string AlertMessage
        {
          get { return TempData[nameof(AlertMessage)]; }
          set { TempData[nameof(AlertMessage)] = value; }
        }
        public bool ShowAlertMessage => !string.IsNullOrEmpty(AlertMessage);
        public Customer Customer { get; set; }
        public bool ShowAdultOnlyThings => (DateTimeOffset.UtcNow - Customer.BirthDate).TotalYears >= 21;
    }
}
@{
    Layout = "_Layout.cshtml";
    ViewData["Title"] = Model.Title;
}

<form method="post" role="form">
    @if (Model.ShowAlertMessage)
    {
        <div class="alert alert-dismissible" role="alert">
            <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            @Model.AlertMessage
        </div>
    }
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Model.Customer.FirstName"></label>
        <input asp-for="Model.Customer.FirstName" class="form-control" />
        <span asp-validation-for="Model.Customer.FirstName" class="help-block text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Model.Customer.LastName"></label>
        <input asp-for="Model.Customer.LastName" class="form-control" />
        <span asp-validation-for="Model.Customer.LastName" class="help-block text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Model.Customer.BirthDate"></label>
        <input asp-for="Model.Customer.BirthDate" class="form-control" />
        <span asp-validation-for="Model.Customer.BirthDate" class="help-block text-danger"></span>
    </div>
    @if (Model.ShowAdultStuff)
    {
    <div class="form-group">
        <label asp-for="Model.Customer.FavoriteDrink"></label>
        <input asp-for="Model.Customer.FavoriteDrink" class="form-control" />
        <span asp-validation-for="Model.Customer.FavoriteDrink" class="help-block text-danger"></span>
    </div>
    }
    <button type="submit" class="btn btn-primary">Save</button>
</form>

@section "Scripts" {
    <partial name="_ValidationScripts" />
}
Member

DamianEdwards commented Jul 14, 2016

Goals

We are trying to:

  • Make dynamic HTML and forms with ASP.NET Core easier, e.g. how many files & concepts required to print Hello World in a page, build a CRUD form, etc.
  • Reduce the number of files and folder-structure required for page-focused MVC scenarios
  • Simplify the code required to implement common page-focused patterns, e.g. dynamic pages, CRUD forms, PRG, etc.
  • Enable the ability to return non-HTML responses when necessary, e.g. 404s
  • Use and expose the existing MVC primitives as much as possible, e.g.:
    • Model binding
    • Model validation
    • Model meta-data
    • Action filters (the ones that make sense)
    • Action results
    • HTML Helpers
    • Tag Helpers
    • Existing Razor directives, e.g. @Inject
    • Layouts & partials
    • _ViewStart.cshtml & _ViewImports.cshtml (should we support _PageStart.cshtml & _PageImports.cshtml instead/as well?)
  • Allow straightforward migration to traditional MVC structure
  • Do this at a reasonable cost, e.g. 1-2 developers during 1.1 release

Non-goals

We are not trying to:

  • Create a scripted page framework to compete with PHP, etc.
  • Hide C# with a DSL in Razor or otherwise
  • Create new primitives that are only applicable here (i.e. they should be usable in traditionally structured MVC apps too)
  • Create undue burdens for us with regards to tooling support, etc.

Enabling

Packaging

The "MVC View Pages" feature will ship in a standalone package, e.g. Microsoft.AspNetCore.Mvc.Razor.ViewPages, that will be referenced by the MVC meta-package, Microsoft.AspNetCore.Mvc. Users of the lower-level Microsoft.AspNetCore.Mvc.Core package can bring in the MVC Pages package to enable it without using the meta-package.

Startup

The IServiceCollection.AddMvc() and IApplicationBuilder.UseMvc() extension methods will enable MVC Pages by default. There will be appropriate extension methods to enable MVC Pages explicitly in the cases where these extensions methods are not used, e.g. when using IServiceCollection.AddMvcCore().

@page directive

MVC pages are represented by CSHTML files that use the new @page directive. This directive instructs the MVC Razor host to treat the CSHTML file as a standalone page for the purposes of request handling.

e.g.

@page
Hello, World! It's @DateTime.Now on the server

@Class block directive

MVC pages can add members to the generated class using the new @class block directive. This directive is effectively an alias of the existing @functions directive. All members added to the class are available to the page during request processing.

Page Actions

Public methods added to the page class in the @class block are candidates for handling request processing, much like action methods on MVC controllers. Methods that match the selection rules become Page Actions, and one (or none) will be selected as the handling Page Action for any given request to the page.

Action Selection

Action selection is based on simple HTTP verb name matching, with support for the prefix On, e.g.: GET requests will match methods called Get or OnGet, POST requests will match methods called Post or OnPost.

Situations that result in more than one action matching are an error should throw an exception (like an ambiguous action selection error in a controller).

If no actions are matched, the page will simply render.

Action Results

Page Actions must return an IActionResult. Typically this will be a ViewResult that renders the current page, e.g. return View();, but any IActionResult is supported. This allows pages to return non-HTML results, e.g. 404 not found, or even JSON or other formats if so desired.

e.g.:

@page
@class {
    public IActionResult OnGet()
    {
        return View();
    }
}
Hello, World!

Parameter Binding

Parameters of Page Actions will be populated using model-binding, just like action methods are in MVC controllers. As such, it will support binding of simple and complex types from the registered value providers, e.g. query string, form, cookie, route data, etc., and model-binders.

e.g.:

@page
@class {
    public IActionResult OnGet(int? id)
    {
        if (id.HasValue && id < 0)
        {
            return NotFound();
        }
        return View();
    }
}
Found!

Properties & Helper Methods

Members in the @class block are available to the page during execution of the page action and during rendering, making it easy to pass data between the two stages.

e.g.

@page
@class {
    public IActionResult OnGet(int? id)
    {
        if (id.HasValue && id < 0)
        {
            return NotFound();
        }
        Id = id;
        return View();
    }

    public int? Id { get; set; }
}
Id = @Id

@model directive

The @model directive can be used to declare the model type for the page. It will generate a Model property on the page class of the declared type, as well as using the model type as the generic type argument for the IHtmlHelper<T> instance available to the page.

@page
@model Customer
@class {
    public IActionResult OnGet(int? id)
    {
        if (id.HasValue && id < 0)
        {
            return NotFound();
        }
        var model = new Customer { Id = id, FirstName = "Jane", LastName = "Smith" };
        return View(model);
    }
}
Customer: @Model.FirstName @Model.LastName

Inline View Models using @model & @Class

Unlike on views, the @model directive will not change the base type of the generated page class to a generic variant. This is to allow the page the ability to declare nested classes in its @class block and use those as the model type with @model.

e.g.

@page
@model ViewModel
@class {
    public IActionResult OnGet(int? id)
    {
        return id.HasValue && id < 0
            ? NotFound()
            : View(new ViewModel {
                Customer = new Customer {
                    Id = id,
                    FirstName = "Jane",
                    LastName = "Smith"
                }
        });
    }

    public class ViewModel
    {
        public Customer Customer { get; set; }
    }
}
Customer: @Model.Customer.FirstName @Model.Customer.LastName

Code-behind class

IDEA: Class of same-name as page file will be automatically used as base type, not sure if this is a good idea, could be hard to make work in expected way reliably.

@inherits directive

The @inherits directive can be used to change the type that the generated page class will derive from. The type specified can be a POCO, or derive from the page base type in MVC (TBD). This way, code can be separated from the CSHTML file and placed in a CS file that it is compiled with the application (rather than as part of the CSHTML compilation process, either at runtime or as part of view pre-compilation).

In the case the base type is a POCO, the existing contextualizing attributes can be placed on properties of the appropriate types to gain access to relevant context objects from MVC, i.e. they will be set to the current instances during page activation, e.g. [HttpContext]. (We might possibly end up with some new context types, e.g. PageContext, in order to make the members appropriate for pages)

e.g.

public class MyPage : Page
{
    public IActionResult OnGet(int? id)
    {
        Id = id;
        return View();
    }

    public int? Id { get; set; }
}
@page
@inherits MyPage
Id was @Id

@Inject directive

Yeah, you can use the @inject directive to generate properties that have instances injected from the DI container, just like in views., e.g. @inject AppDbContext Db

@{ } code blocks

They work just like they do in views and run as part of page rendering.

Page URLs

The path to the page file from the configured pages root path (defaults to IHostingEnvironment.ContentRootPath), including the file name, but excluding the file extension, becomes the URL that the page will be served in response to. Pages with a file name of "Index.cshtml" will also act as the default page for the sub-path (folder) they are in, e.g.:

Page Path URL
/Foo.cshtml /foo
/Foo/Bar.cshtml /foo/bar
/Index.cshtml /index
/Index.cshtml /
/Foo/Index.cshtml /foo/

Explicit routing using conventional routes

TODO: routeBuilder.MapPageRoute("customers/{id}", "Customer.cshtml")

Explicit routing using attribute routes

TODO: Using attribute routes on the base class when using @inherits

Anti-forgery (CSRF protection)

TODO

Examples

Hello World

Project Structure

Index.cshtml
Program.cs
project.json

Program.cs

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace HelloWorld
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .SetContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }

        public class Startup
        {
            public void ConfigureServices(IServiceCollection services) => services.AddMvcPages();
            public void Configure(IApplicationBuilder app) => app.UseMvcPages();
        }
    }
}

Index.cshtml

@page
Hello, World! It's @DateTime.Now

Single-page CRUD

Project Structure

Data/
    AppDbContext.cs
    Customer.cs
wwwroot/
    [static files]
_Layout.cshtml
_ValidationScripts.cshtml
appsettings.json
Customers.cshtml
Program.cs
project.json
Startup.cs

Program.cs

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace HelloWorld
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .SetContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }

        public class Startup
        {
            public Startup(IHostingEnvironment env)
            {
                var builder = new ConfigurationBuilder()
                    .SetBasePath(env.ContentRootPath)
                    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
                Configuration = builder.Build();
            }

            public IConfigurationRoot Configuration { get; }

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddDbContext<AppDbContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

                services.AddMvcPages();
            }

            public void Configure(IApplicationBuilder app)
            {
                app.UseStaticFiles();
                app.UseMvcPages();
            }
        }
    }
}

Data/Customer.cs

namespace MyApp.Data
{
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string DateTimeOffset BirthDate { get; set; }
        public string FavoriteDrink { get; set; }
    }
}

Data/AppDbContext.cs

namespace MyApp.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {

        }

        public DbSet<Customer> Customers { get; set; }
    }
}

Customers.cshtml

@page
@using MyApp.Data
@model ViewModel
@inject AppDbContext Db
@class {
    public IActionResult OnGet(int? id)
    {
        var customer = id.HasValue ? await Db.Customers.SingleOrDefaultAsync(c => c.Id == id) : (Customer)null;
        return id.HasValue && customer == null ?
            NotFound() :
            View(new ViewModel {
                Customer = customer,
                Title = customer != null ? $"Edit Customer {customer?.Id}" : "New Customer"
            });
    }

    public IActionResult OnPost(ViewModel model)
    {
        if (!ModelState.IsValid())
        {
            // Model errors, just return the view to show them the errors
            model.Title = "Edit Customer";
            return View(model);
        }

        if (!model.Customer.Id.HasValue)
        {
            // Create
            Db.Customers.Add(model.Customer);
            ViewModel.AlertMessage = $"New customer {model.Customer.Id} created successfully!";
        }
        else
        {
            // Update
            Db.Attach(model.Customer, EntityState.Changed);
            ViewModel.AlertMessage = $"Customer {model.Customer.Id} updated successfully!";
        }

        await Db.SaveChangesAsync();

        return Reload(new { id = model.Customer.Id });
    }

    public class ViewModel
    {
        public string Title { get; set; }
        public string AlertMessage
        {
          get { return TempData[nameof(AlertMessage)]; }
          set { TempData[nameof(AlertMessage)] = value; }
        }
        public bool ShowAlertMessage => !string.IsNullOrEmpty(AlertMessage);
        public Customer Customer { get; set; }
        public bool ShowAdultOnlyThings => (DateTimeOffset.UtcNow - Customer.BirthDate).TotalYears >= 21;
    }
}
@{
    Layout = "_Layout.cshtml";
    ViewData["Title"] = Model.Title;
}

<form method="post" role="form">
    @if (Model.ShowAlertMessage)
    {
        <div class="alert alert-dismissible" role="alert">
            <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            @Model.AlertMessage
        </div>
    }
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Model.Customer.FirstName"></label>
        <input asp-for="Model.Customer.FirstName" class="form-control" />
        <span asp-validation-for="Model.Customer.FirstName" class="help-block text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Model.Customer.LastName"></label>
        <input asp-for="Model.Customer.LastName" class="form-control" />
        <span asp-validation-for="Model.Customer.LastName" class="help-block text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Model.Customer.BirthDate"></label>
        <input asp-for="Model.Customer.BirthDate" class="form-control" />
        <span asp-validation-for="Model.Customer.BirthDate" class="help-block text-danger"></span>
    </div>
    @if (Model.ShowAdultStuff)
    {
    <div class="form-group">
        <label asp-for="Model.Customer.FavoriteDrink"></label>
        <input asp-for="Model.Customer.FavoriteDrink" class="form-control" />
        <span asp-validation-for="Model.Customer.FavoriteDrink" class="help-block text-danger"></span>
    </div>
    }
    <button type="submit" class="btn btn-primary">Save</button>
</form>

@section "Scripts" {
    <partial name="_ValidationScripts" />
}
@mikebrind

This comment has been minimized.

Show comment
Hide comment
@mikebrind

mikebrind Jul 14, 2016

Action selection: Wouldn't it make more sense to use attributes here rather than verb name matching? It's an easy enough concept to grok and will facilitate migration to MVC.

Action selection: Wouldn't it make more sense to use attributes here rather than verb name matching? It's an easy enough concept to grok and will facilitate migration to MVC.

@ctyar

This comment has been minimized.

Show comment
Hide comment
@ctyar

ctyar Jul 14, 2016

Contributor

Sorry I know this is not productive but this sample

@page
@class {
    public IActionResult OnGet()
    {
        return View();
    }
}
Hello, World!

reminds me of php's dark ages

<?php
include("../../externs/includes/connexio.php");

$result = mysqli_query($con, "SELECT * FROM myTable");

while ($row = mysqli_fetch_array($result)) {
    $id = $row["id"];
    $title = $row["title"];
    $subtitle = $row["subtitle"];
?>  

<div class="title" id="<?php echo $id; ?>"><?php echo $title; ?> </div>
<div class="subtitle"><?php echo $subtitle;?> </div>
<br>

<?php 
}
?>
Contributor

ctyar commented Jul 14, 2016

Sorry I know this is not productive but this sample

@page
@class {
    public IActionResult OnGet()
    {
        return View();
    }
}
Hello, World!

reminds me of php's dark ages

<?php
include("../../externs/includes/connexio.php");

$result = mysqli_query($con, "SELECT * FROM myTable");

while ($row = mysqli_fetch_array($result)) {
    $id = $row["id"];
    $title = $row["title"];
    $subtitle = $row["subtitle"];
?>  

<div class="title" id="<?php echo $id; ?>"><?php echo $title; ?> </div>
<div class="subtitle"><?php echo $subtitle;?> </div>
<br>

<?php 
}
?>
@DamianEdwards

This comment has been minimized.

Show comment
Hide comment
@DamianEdwards

DamianEdwards Jul 14, 2016

Member

@mikebrind the verb attributes will be supported when using @inherits to place the page actions in a CS file. Supporting them in the CSHTML creates issues around lifecycle and route discovery that we'd rather not tackle in this release.

We could create new attributes for this purpose, but we're trying to limit the number of new constructs we create, especially when they are similar to those that already exist in MVC.

For now, we plan to keep the pattern-based selection for page actions in CSHTML, and attribute routing when they're in CS.

Member

DamianEdwards commented Jul 14, 2016

@mikebrind the verb attributes will be supported when using @inherits to place the page actions in a CS file. Supporting them in the CSHTML creates issues around lifecycle and route discovery that we'd rather not tackle in this release.

We could create new attributes for this purpose, but we're trying to limit the number of new constructs we create, especially when they are similar to those that already exist in MVC.

For now, we plan to keep the pattern-based selection for page actions in CSHTML, and attribute routing when they're in CS.

@mikebrind

This comment has been minimized.

Show comment
Hide comment
@mikebrind

mikebrind Jul 14, 2016

@DamianEdwards That makes sense. I agree that it's a good idea not to create twins of existing MVC artefacts that actually differ in implementation.

@DamianEdwards That makes sense. I agree that it's a good idea not to create twins of existing MVC artefacts that actually differ in implementation.

@mikebrind

This comment has been minimized.

Show comment
Hide comment
@mikebrind

mikebrind Jul 14, 2016

Appreciate it is just an idea at this stage, but what kind of problems would the "code-behind" (page controller) feature attempt to solve?

Appreciate it is just an idea at this stage, but what kind of problems would the "code-behind" (page controller) feature attempt to solve?

@rynowak

This comment has been minimized.

Show comment
Hide comment
@rynowak

rynowak Jul 14, 2016

Member

@mikebrind - the code-behind is part of your app and instantiable/unit-testable

Member

rynowak commented Jul 14, 2016

@mikebrind - the code-behind is part of your app and instantiable/unit-testable

@mikebrind

This comment has been minimized.

Show comment
Hide comment
@mikebrind

mikebrind Jul 14, 2016

@rynowak If people want to unit test their app, shouldn't they be looking towards MVC instead?

@rynowak If people want to unit test their app, shouldn't they be looking towards MVC instead?

@rynowak

This comment has been minimized.

Show comment
Hide comment
@rynowak

rynowak Jul 14, 2016

Member

If people want to unit test their app, shouldn't they be looking towards MVC instead?

We want to provide a few more in steps in between what MVC can do today and a simplest possible solution. It wouldn't be good if the choice is binary - either you get Controllers all the goodness - or you get just Razor.

I also expect that the the code-behind might become the place to put a lot of the code that goes into a view model today, which usually gets unit tested.

Also, it's just some folks preference that c# goes in .cs files. That's totally cool, we want to provide a few more options.

Member

rynowak commented Jul 14, 2016

If people want to unit test their app, shouldn't they be looking towards MVC instead?

We want to provide a few more in steps in between what MVC can do today and a simplest possible solution. It wouldn't be good if the choice is binary - either you get Controllers all the goodness - or you get just Razor.

I also expect that the the code-behind might become the place to put a lot of the code that goes into a view model today, which usually gets unit tested.

Also, it's just some folks preference that c# goes in .cs files. That's totally cool, we want to provide a few more options.

@sqmgh

This comment has been minimized.

Show comment
Hide comment
@sqmgh

sqmgh Jul 14, 2016

It feels to me like this is going to end up causing confusion and ambiguity for people who are trying to get into the space.

If someone is searching for guides or answers using a search engine, or something like StackOverflow you already run into problems with conflicting versions. Unless you're careful you get old incompatible versions of MVC, and unless you are very careful you get examples from the betas which are also not compatible.
That's not so bad though, because at least those won't compile, and can be explained away with "It's an old version".
However, this is going to be adding another set of examples which will show up while searching for 'aspnet' that will work just fine. I can very easily see people who aren't sure what they are doing creating confusing Frankencode projects by stitching together things that 'work', and having it quickly become very hard to maintain. Which could quite possibly reflect poorly on the whole experience.

I'm not saying the idea is necessarily a bad one, but you need to be very careful with the branding. Noting that having 'Web Pages' work (though I acknowledge it could just be this one issue) being done in the MVC repository doesn't feel like it's off to a great start.

sqmgh commented Jul 14, 2016

It feels to me like this is going to end up causing confusion and ambiguity for people who are trying to get into the space.

If someone is searching for guides or answers using a search engine, or something like StackOverflow you already run into problems with conflicting versions. Unless you're careful you get old incompatible versions of MVC, and unless you are very careful you get examples from the betas which are also not compatible.
That's not so bad though, because at least those won't compile, and can be explained away with "It's an old version".
However, this is going to be adding another set of examples which will show up while searching for 'aspnet' that will work just fine. I can very easily see people who aren't sure what they are doing creating confusing Frankencode projects by stitching together things that 'work', and having it quickly become very hard to maintain. Which could quite possibly reflect poorly on the whole experience.

I'm not saying the idea is necessarily a bad one, but you need to be very careful with the branding. Noting that having 'Web Pages' work (though I acknowledge it could just be this one issue) being done in the MVC repository doesn't feel like it's off to a great start.

@Eilon

This comment has been minimized.

Show comment
Hide comment
@Eilon

Eilon Sep 29, 2016

Member

Moving to 1.2.0 milestone.

Member

Eilon commented Sep 29, 2016

Moving to 1.2.0 milestone.

@dustinmoris

This comment has been minimized.

Show comment
Hide comment
@dustinmoris

dustinmoris Feb 24, 2017

Not sure if someone has already asked that question, but is it intended to work outside of MVC as well, so it can be used from an ASP.NET Core application without having to install the MVC NuGet package?

It would be awesome if (when completed) I could easily use it from inside ASP.NET Core Lambda. Currently all efforts to bring Razor to non MVC frameworks have been duplicated by multiple projects and none of them is really great or easily reusable.

For example Nancy has written their own implementation which is tied to Nancy, Suave is using RazorEngine which doesn't seem to be maintained anymore and doesn't support .NET Core either, and then there is a rather new RazorLight project which seems to be the best option for .NET Core at the moment, but it would be still nice to have an official Microsoft library which can consolidate all of these individual efforts and bring the same level of Razor support to multiple frameworks.

Reading the spec at the top and looking at RazorPages I think it's not far away to be completely decoupled from MVC (logically decoupled from MVC, but still part of the default MVC package and integrated into the MVC so that the current experience is not worse off).

What do you think?

dustinmoris commented Feb 24, 2017

Not sure if someone has already asked that question, but is it intended to work outside of MVC as well, so it can be used from an ASP.NET Core application without having to install the MVC NuGet package?

It would be awesome if (when completed) I could easily use it from inside ASP.NET Core Lambda. Currently all efforts to bring Razor to non MVC frameworks have been duplicated by multiple projects and none of them is really great or easily reusable.

For example Nancy has written their own implementation which is tied to Nancy, Suave is using RazorEngine which doesn't seem to be maintained anymore and doesn't support .NET Core either, and then there is a rather new RazorLight project which seems to be the best option for .NET Core at the moment, but it would be still nice to have an official Microsoft library which can consolidate all of these individual efforts and bring the same level of Razor support to multiple frameworks.

Reading the spec at the top and looking at RazorPages I think it's not far away to be completely decoupled from MVC (logically decoupled from MVC, but still part of the default MVC package and integrated into the MVC so that the current experience is not worse off).

What do you think?

@DamianEdwards

This comment has been minimized.

Show comment
Hide comment
@DamianEdwards

DamianEdwards Feb 24, 2017

Member

@dustinmoris it's built in to MVC and is built on top of many of the primitives in MVC. In short, it can't be split out. I'm not saying there won't ever be a time we're able to split out all the bits of MVC into separate parts (e.g. model binding, validation, tag helpers, view engine, etc.) but it's very unlikely. This is part of MVC.

Member

DamianEdwards commented Feb 24, 2017

@dustinmoris it's built in to MVC and is built on top of many of the primitives in MVC. In short, it can't be split out. I'm not saying there won't ever be a time we're able to split out all the bits of MVC into separate parts (e.g. model binding, validation, tag helpers, view engine, etc.) but it's very unlikely. This is part of MVC.

@villanus

This comment has been minimized.

Show comment
Hide comment
@villanus

villanus Feb 25, 2017

at the next comunity stand up can you spend some time showing this off?

at the next comunity stand up can you spend some time showing this off?

@cyrusel

This comment has been minimized.

Show comment
Hide comment
@cyrusel

cyrusel Feb 25, 2017

@DamianEdvards, when mvc viewpages is ready to use? please give some information about which step is it. thank you

cyrusel commented Feb 25, 2017

@DamianEdvards, when mvc viewpages is ready to use? please give some information about which step is it. thank you

@DamianEdwards

This comment has been minimized.

Show comment
Hide comment
@DamianEdwards

DamianEdwards Feb 25, 2017

Member

@cyrusel it will be part of the 2.0 release. We're aiming to release a preview around May, and finish it a month or two later.

Member

DamianEdwards commented Feb 25, 2017

@cyrusel it will be part of the 2.0 release. We're aiming to release a preview around May, and finish it a month or two later.

@cyrusel

This comment has been minimized.

Show comment
Hide comment
@cyrusel

cyrusel Feb 25, 2017

@DamianEdwards
good luck, please make it simple as web pages is to encourage php guys to leave it and join asp.net core. thank you so much man.

cyrusel commented Feb 25, 2017

@DamianEdwards
good luck, please make it simple as web pages is to encourage php guys to leave it and join asp.net core. thank you so much man.

@villanus

This comment has been minimized.

Show comment
Hide comment
@villanus

villanus Feb 25, 2017

@DamianEdwards as cyrusel pointed out it might be good to have a full beginner training path. For js, php, classic asp devs, who want to jump into core without mvc.

It would be nice if the training vision was to star on razorpages, then add more advanced and eventually learn MVC.

I see how this would be useful for both evangelists, and the comunnity.

Versus just having a different framework

@DamianEdwards as cyrusel pointed out it might be good to have a full beginner training path. For js, php, classic asp devs, who want to jump into core without mvc.

It would be nice if the training vision was to star on razorpages, then add more advanced and eventually learn MVC.

I see how this would be useful for both evangelists, and the comunnity.

Versus just having a different framework

@danroth27 danroth27 changed the title from MVC View Pages to Razor Pages Mar 3, 2017

@davidfowl davidfowl referenced this issue in aspnet/Home Mar 21, 2017

Closed

WebForms in Asp.NET Core #1961

@rynowak rynowak added this to Needs Design in Razor Pages 2.0.0-preview1 Apr 10, 2017

@rynowak rynowak added 3 - Done and removed 2 - Working labels Apr 27, 2017

@rynowak rynowak closed this Apr 27, 2017

@rynowak rynowak moved this from Design to Done in Razor Pages 2.0.0-preview1 Apr 27, 2017

@iamzm

This comment has been minimized.

Show comment
Hide comment
@iamzm

iamzm May 21, 2017

Will this not be less secure and also my concern is it will make page rendering slow ?? Can anyone help me

iamzm commented May 21, 2017

Will this not be less secure and also my concern is it will make page rendering slow ?? Can anyone help me

@villanus

This comment has been minimized.

Show comment
Hide comment
@villanus

villanus May 21, 2017

Why would it be less secure?

Why would it be less secure?

@Eilon

This comment has been minimized.

Show comment
Hide comment
@Eilon

Eilon May 22, 2017

Member

@iamzm the security is the same as the rest of ASP.NET Core. If you have any specific concerns, please let us know! If you think you've found a security bug, please contact secure@microsoft.com with a detailed bug report.

The performance in general should be roughly the same as ASP.NET Core MVC, because Razor Pages is built on ASP.NET Core MVC. If you have observed performance issues, please log a bug in this repo and we will investigate.

Thanks!

Member

Eilon commented May 22, 2017

@iamzm the security is the same as the rest of ASP.NET Core. If you have any specific concerns, please let us know! If you think you've found a security bug, please contact secure@microsoft.com with a detailed bug report.

The performance in general should be roughly the same as ASP.NET Core MVC, because Razor Pages is built on ASP.NET Core MVC. If you have observed performance issues, please log a bug in this repo and we will investigate.

Thanks!

@cyrusel

This comment has been minimized.

Show comment
Hide comment
@cyrusel

cyrusel May 22, 2017

@eliton There is something wrong related to microsoft.dotnetcore.mvc.taghelpers! if you remove it from _viewstart and submit the form with Post method, request validation token won't be generated and you will get bad request (error 400). In fact either unti-forgery should not be relied on tagHelpers or there should be a way to disable unti-forgery.
thanks

cyrusel commented May 22, 2017

@eliton There is something wrong related to microsoft.dotnetcore.mvc.taghelpers! if you remove it from _viewstart and submit the form with Post method, request validation token won't be generated and you will get bad request (error 400). In fact either unti-forgery should not be relied on tagHelpers or there should be a way to disable unti-forgery.
thanks

@cyrusel

This comment has been minimized.

Show comment
Hide comment
@DamianEdwards

This comment has been minimized.

Show comment
Hide comment
@DamianEdwards

DamianEdwards May 22, 2017

Member

@cyrusel if you remove the Tag Helpers registration then the anti-forgery token will not be rendered with the form, hence the failure on POST as the page expects a token by default. You can disable anti-forgery but it's not recommended.

Member

DamianEdwards commented May 22, 2017

@cyrusel if you remove the Tag Helpers registration then the anti-forgery token will not be rendered with the form, hence the failure on POST as the page expects a token by default. You can disable anti-forgery but it's not recommended.

@cyrusel

This comment has been minimized.

Show comment
Hide comment
@cyrusel

cyrusel May 22, 2017

@DamianEdwards
First.. Thanks for your huge efforts and creating such amazing model.
How can I disable anti-forgery? bcuz I want to implement my custom code instead.
Also I think the way you design, it's mandatory to use tagHelpers otherwise we lose anti-forgery feature. Maybe somebody doesn't want to use tagHelpers!
Thanks again

cyrusel commented May 22, 2017

@DamianEdwards
First.. Thanks for your huge efforts and creating such amazing model.
How can I disable anti-forgery? bcuz I want to implement my custom code instead.
Also I think the way you design, it's mandatory to use tagHelpers otherwise we lose anti-forgery feature. Maybe somebody doesn't want to use tagHelpers!
Thanks again

@rynowak

This comment has been minimized.

Show comment
Hide comment
@rynowak

rynowak May 22, 2017

Member
            services.AddMvc().AddRazorPagesOptions(o =>
            {
                o.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
            });

This will turn off autiforgery token validation for pages.

You can also still generate an antiforgery token the old fashioned way using @Html.AntiForgeryToken()

Member

rynowak commented May 22, 2017

            services.AddMvc().AddRazorPagesOptions(o =>
            {
                o.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
            });

This will turn off autiforgery token validation for pages.

You can also still generate an antiforgery token the old fashioned way using @Html.AntiForgeryToken()

@iamzm

This comment has been minimized.

Show comment
Hide comment
@iamzm

iamzm May 23, 2017

@Eilon Thanks for making things clear. Regarding slowness i meant if we are going to write our business logic in Page itself .. Don't you think it can be improper pipeline ? Can you please explain a little

iamzm commented May 23, 2017

@Eilon Thanks for making things clear. Regarding slowness i meant if we are going to write our business logic in Page itself .. Don't you think it can be improper pipeline ? Can you please explain a little

@iamzm

This comment has been minimized.

Show comment
Hide comment
@iamzm

iamzm May 23, 2017

@DamianEdwards What if i have to write non-CRUD method ?? Like you guys are suggesting On- ??

iamzm commented May 23, 2017

@DamianEdwards What if i have to write non-CRUD method ?? Like you guys are suggesting On- ??

@iamzm

This comment has been minimized.

Show comment
Hide comment
@iamzm

iamzm May 23, 2017

@DamianEdwards What if i want to redirectto("www.facbook.com") ? any possible way here ?

iamzm commented May 23, 2017

@DamianEdwards What if i want to redirectto("www.facbook.com") ? any possible way here ?

@mikebrind

This comment has been minimized.

Show comment
Hide comment
@mikebrind

mikebrind May 23, 2017

@iamzm If you want to redirect to another site, you can use Response.Redirect or return an MVC RedirectResult:

public void OnPost()
{
    Response.Redirect("http://www.facebook.com");
}

or

public RedirectResult OnPost()
{
    return Redirect("http://www.facebook.com");
}

There is no reason for putting business logic inline. You place it in the page controller or in a separate layer which is called in the page controller. If you do put your business logic inline, your application is unlikely to experience any slowness in running. It may take longer to compile the page on first run, and it is likely to make it more difficult to maintain in the longer run if it's a true business app, and not just a simple blog.

You can structure your Razor Pages application in exactly the same way as an MVC application, except that you have one controller per view in Razor Pages instead of one controller per feature or business area in MVC. Most of the rest of Razor Pages is the same ASP.NET Core as MVC.

@iamzm If you want to redirect to another site, you can use Response.Redirect or return an MVC RedirectResult:

public void OnPost()
{
    Response.Redirect("http://www.facebook.com");
}

or

public RedirectResult OnPost()
{
    return Redirect("http://www.facebook.com");
}

There is no reason for putting business logic inline. You place it in the page controller or in a separate layer which is called in the page controller. If you do put your business logic inline, your application is unlikely to experience any slowness in running. It may take longer to compile the page on first run, and it is likely to make it more difficult to maintain in the longer run if it's a true business app, and not just a simple blog.

You can structure your Razor Pages application in exactly the same way as an MVC application, except that you have one controller per view in Razor Pages instead of one controller per feature or business area in MVC. Most of the rest of Razor Pages is the same ASP.NET Core as MVC.

@iamzm

This comment has been minimized.

Show comment
Hide comment
@iamzm

iamzm May 23, 2017

Thanks @mikebrind what if i want to write non-crud action in Page ? Like instead of OnPost, OnGet, OnDelete .. Is it possible i can write my custom method like verifyemail ? So can i follow like

OnVerifyEmail ??

iamzm commented May 23, 2017

Thanks @mikebrind what if i want to write non-crud action in Page ? Like instead of OnPost, OnGet, OnDelete .. Is it possible i can write my custom method like verifyemail ? So can i follow like

OnVerifyEmail ??

@mikebrind

This comment has been minimized.

Show comment
Hide comment
@mikebrind

mikebrind May 23, 2017

You can add any method to your page. This is how you might do it:

@{
    @functions{
        
        public void OnPost(string email)
        {
            if(VerifyEmail(email))
	    {
	         Response.Redirect("http://www.facebook.com");
	    }
        }

        public bool VerifyEmail(string email)
        {
            return Regex.IsMatch(email, @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
        }
    }
}

You can add any method to your page. This is how you might do it:

@{
    @functions{
        
        public void OnPost(string email)
        {
            if(VerifyEmail(email))
	    {
	         Response.Redirect("http://www.facebook.com");
	    }
        }

        public bool VerifyEmail(string email)
        {
            return Regex.IsMatch(email, @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
        }
    }
}
@iamzm

This comment has been minimized.

Show comment
Hide comment
@iamzm

iamzm May 23, 2017

@mikebrind Thanks for helping me out ! I had also same idea but wanted to be sure

iamzm commented May 23, 2017

@mikebrind Thanks for helping me out ! I had also same idea but wanted to be sure

@RickStrahl

This comment has been minimized.

Show comment
Hide comment
@RickStrahl

RickStrahl May 27, 2017

Is there any discussion anywhere about some mechanism to embed RazorPages without running a Web application? Essentially using razor as a stream/string templating engine inside of a another application that is not a Web app or even just a sub-system that doesn't know about a Web app?

Is there any discussion anywhere about some mechanism to embed RazorPages without running a Web application? Essentially using razor as a stream/string templating engine inside of a another application that is not a Web app or even just a sub-system that doesn't know about a Web app?

@iamzm

This comment has been minimized.

Show comment
Hide comment
@iamzm

iamzm May 27, 2017

@RickStrahl Libraries are must i think to include to get RazorPages work even in another outside application

iamzm commented May 27, 2017

@RickStrahl Libraries are must i think to include to get RazorPages work even in another outside application

@rynowak

This comment has been minimized.

Show comment
Hide comment
@rynowak

rynowak May 30, 2017

Member

@RickStrahl

We have our own usage of Razor like this here. This is what powers the developer exception page and the like.

There's a sample using Razor + someof MVC to generate templated emails here.
https://github.com/aspnet/Entropy/tree/dev/samples/Mvc.RenderViewToString

If there's something in particular that we need to improve for this usage of Razor, please open issues.

Member

rynowak commented May 30, 2017

@RickStrahl

We have our own usage of Razor like this here. This is what powers the developer exception page and the like.

There's a sample using Razor + someof MVC to generate templated emails here.
https://github.com/aspnet/Entropy/tree/dev/samples/Mvc.RenderViewToString

If there's something in particular that we need to improve for this usage of Razor, please open issues.

@rynowak

This comment has been minimized.

Show comment
Hide comment
@rynowak

rynowak May 30, 2017

Member

Locking this ancient issue because we've implemented it!. If you have something to talk about please open a new issue instead of posting on this one, thanks!

Member

rynowak commented May 30, 2017

Locking this ancient issue because we've implemented it!. If you have something to talk about please open a new issue instead of posting on this one, thanks!

@aspnet aspnet locked and limited conversation to collaborators May 30, 2017

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.