Saturday, December 9, 2017

A little trick to create Vue.js single file reusable components without NPM and Webpack

I have started using VUE.JS to jazz up my front ends in Core MVC .NET and all I can say is WOW!

After cringing every time I had to use JQuery to make my page do things without a postback (Ajax) I kept looking for a solution.

Now don't get me wrong, I am no javascript or Node or NPM or ... expert. I barely even understand these technologies and especially how to make them work inside Visual Studio, so not everything in VUE works 100%.

Once I got the hang of Vue.js I quickly saw how COMPONENTS would be the key to creating reusable code. I especially like the idea of Single File Components, as they are self-contained and include the Template, Javascript, and CSS.

I am not creating SPAs, but load each CSHTML page like a normal .NET application. Inside the page is where the magic of Vue.js happens. I was disappointed when trying to use Single Page Components and realized I needed a bunch of additional things I didn't even understand (NPM, Webpack, ???, and ...). There seemed to be no end to items I had to add to Visual Studio, and all I wanted to do was add reusable Vue.js Components that I could include on certain pages.

So here is my trick, create Self-Contained CSHTML pages with the Vue.js Component and insert them in the page like a Partial View using @Html.Partial("vuejs-component.cshtml").

This worked for me, but if you have a better solution please let the community know.

The Vue.js Sample Code is here: https://vuejs.org/v2/examples/grid-component.html

And this is how I used it inside my CSHTML Page...

<div id="app">
<div>
<h3>{{test}}</h3>
</div>

<sob-grid :data="gridData"
:columns="gridColumns"
:filter-key="searchQuery">
</sob-grid>

</div>

@section Scripts {

@Html.Partial("~/vue/sob-grid.cshtml")

<script>

new Vue({
el: '#app',
data: {
test: 'Test Grid Component',
searchQuery: '',
gridColumns: ['name', 'power'],
gridData: [
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 }
]
}
})

</script>

}

Use Razor Pages as a WebApi

One of the best features of Razor Pages is that you can make changes in the editor to your C# code, save, and refresh the browser. You don't have to stop the debugger, make your changes, and then start the debugger again, which is painfully slow (about 20 seconds to start each time). To me, this is the best developer environment around.

So, what all can you do inside the Razor Pages function { } section? Well, I'm not exactly sure what can go here. I have tried putting Controller and WebController methods, but the routing doesn't seem to work, so I just used normal Razor Page methods with OnGet??? and OnPut??? method names.

Here is an example of my Razor Page WebApi. The trick here is to use [IgnoreAntiforgeryToken(Order = 1001)]. This turns OFF form token authorization. If you want this extra security you will have to use jquery to get the form token and then pass it in your header (trust me, turn this off for now!). Look below the code for an example of how to call it using Axios.

@page "{handler?}"

@using bbTickets.Controller
@using Microsoft.AspNetCore.Mvc.RazorPages
@using System
@using System.Linq
@using System.Threading.Tasks
@using Microsoft.AspNetCore.Mvc
@using Microsoft.EntityFrameworkCore
@using Microsoft.AspNetCore.Authorization

@model Tickets

@functions {

[Authorize]
[IgnoreAntiforgeryToken(Order = 1001)]
public class Tickets : PageModel
{
    private readonly ApplicationDbContext _db;

    public Tickets(ApplicationDbContext context)
    {
        _db = context;
    }

    public JsonResult OnGetTicketList(int Top = 1)
    {
        var data = _db.Tickets.Take(Top).OrderBy(x => x.CreatedAt);
        return new JsonResult(data);
    }

    public async Task<IActionResult> OnPutTicketUpdate([FromBody] _tickets _tickets)
    {
        if (!ModelState.IsValid)
        {
            return new JsonResult(new { isValid = false, error = "modelState" });
        }
        var orgticket = await _db.Tickets.FirstOrDefaultAsync(x => x.Id == _tickets.Id);
        if (orgticket == null)
        {
            return new JsonResult(new { isValid = false, error = "idNotFound" });
        }
        orgticket.LastUpdatedAt = DateTime.UtcNow;
        orgticket.ContactName = _tickets.ContactName;
        orgticket.TicketSubject = _tickets.TicketSubject;
        orgticket.AssignedStaffEmail = _tickets.AssignedStaffEmail;
        orgticket.AssignedStaffName = _tickets.AssignedStaffName.Trim();
        try
        {
            await _db.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            {
                throw;
            }
        }

        return new OkResult();
    }
}
}

Calling the API in Javascript.

axios.get('/vapi/Tickets/TicketList', {
                        params: {
                            Top: top
                        }
                    })
                        .then(function (response) {
                            self.tickets = response.data;                            
                        })
                        .catch(function (error) {
                            console.log(error);
                        });

axios.put('/vapi/Tickets/TicketUpdate', self.tick) 
                        .then(function (response) {
                            console.log(response);
                        })
                        .catch(function (error) {
                            console.log(error);
                        });

Here is the Solution Tree. Notice the VAPI folder under Pages. You can call this anything you want, the trick is to put your api files under Pages for proper routing.