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.


Wednesday, March 1, 2017

Using DocumentDB with C# my Way

After struggling several weeks trying to come up with the correct syntax using DocumentDB, C#, and MVC I finally got it.

I like to create an IO class that contains all the methods for CRUD and Queries. My Controllers call these methods for all data io. The nice thing is that you can start out with a single project and then create a DLL or API later and the controller does not have to change.

There are plenty of examples out there including the QuickStart app created in the portal. I could not get most of these to work properly. I assume that the database and collection have already been created in the portal. So here is my solution...

Sample IO and Controller Class:

public class ioPt
    {
        private const string EndpointUri = "https://yourid.documents.azure.com:443/";
        private const string PrimaryKey = "yourkey";
        private static readonly string DatabaseId = "yourdatabase";
        private static readonly string CollectionId = "yourcollection";
        private static readonly Uri CollectionLink = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId);

        public List<ptTenant> TenantList()
        {
            using (var _client = new DocumentClient(new Uri(EndpointUri), PrimaryKey))
            {
                var data = _client.CreateDocumentQuery<ptTenant>(CollectionLink).Where(x => x.TypeId == nameof(ptTenant))
                    .OrderBy(x => x.Name).ToList();
                return data;
            }
        }

        public async Task<ptTenant> TenantAdd(ptTenant tenant)
        {
            using (var _client = new DocumentClient(new Uri(EndpointUri), PrimaryKey))
            {
                await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), tenant);
            }
            return tenant;
        }
    }

 public class HostController : Controller
    {
        ioPt zio = new ioPt();

        public ActionResult Index()
        {
            var data = zio.TenantList();
            return View(data);
        }

        public IActionResult NewTenant()
        {
            var tenant = new ptTenant();
            return View(tenant);
        }

        [HttpPost]
        public async Task<ActionResult> NewTenant(ptTenant tenant)
        {
            if (ModelState.IsValid)
            {
                await zio.TenantAdd(tenant);
                return RedirectToAction("Index");
            }
            return View(tenant);
        }

    }