Friday, July 27, 2018

ASP .NET CORE with Angular 6 Template and Swagger

This may be one of those forehead-slapping obvious things that should never have slowed me down so much. I wanted to add Swagger usage to my new .Net Core Angular application, and kept getting issues with the Swagger user interface being the start and default page of the site. Of course, if you use Swagger, you know you would prefer to only get to it by typing your site's url, with "/swagger" appended to the end. This way it sits quietly in the background as a developer/analyst tool.

Well, the Captain Obvious setting that works for me is, change the RoutePrefix from String.Empty to "swagger".

c.RoutePrefix = "swagger";

The site loads to the expected default page of the SPA and runs the route I specified for empty string, which is just the Index.html in the ClientApp folder. Only when I change the URL in the browser do I get to Swagger.

I read probably 10 websites about how to do this, and you can guess, none of them made it simple and most just confused the issue big time. People wrote up something called a "SPA fallback" configuration. I don't know what that is, and apparently, don't need to.



Wednesday, November 01, 2017

StackOverflow is starting to suck..

And so is the whole cyberspace of help with technical or programming problems.

Take this example :

https://stackoverflow.com/questions/35204499/hangfire-get-last-execution-time

The original poster asked a simple question about how to get the last run time of a job in the wonderful tool called Hangfire.

The problem is a challenge, because within the job code, the date is already calculated and saved upon the job. So you can't get the prior run time.

The responder suggested simply decrementing an integer counter for jobs within Hangfire and reading that job's run time. That only works in the case where ONLY ONE job exists in the tool. And nobody is going to use Hangfire to do just one thing, in most cases. That's a heavy way to get something done.

Now I tried to reply to this on StackOverflow, only to be told I don't have enough reputation points.

This is the chicken and egg syndrome big time that the internet is supposed to help stop. Stop denying people opportunity just because they haven't done something before.

The rest of the internet is chock full of crappy answers to this, and other problems in general. Some try to charge for those crappy answers.

For example, this guy is also dead wrong about how this works :

http://www.niceonecode.com/Q-A/DotNet/MVC/HangFire-get-last-and-next-execution-datetime/20439

Thanks


Monday, July 10, 2017

ANSI NULLS what?

Most people working with SQL Server are probably up to date on this; but I figure it is worth mentioning.

SQL Server treats NULL values in some un-intuitive ways.

Consider this in my 'Where' clause :



code NOT IN (32,37,33,39,40,41,47,48,83,94,93,95,501)


I would think this automatically means, anything that is not one of these numbers, including any NULL. However, that is not how it works. It ignored NULL, removing valuable data from the returned data, so I had to add an explicit inclusion of that potential table value :



code NOT IN (32,37,33,39,40,41,47,48,83,94,93,95,501) OR code IS NULL


Microsoft has said, all future versions of SQL Server will use ANSI NULLS ON with no option to turn it on or off. So what? You might ask. Well, this means that " When SET ANSI_NULLS is ON, a SELECT statement that uses WHERE column_name = NULL returns zero rows even if there are null values in column_name"

The best guidance I found in the MSDN article was " For a script to work as intended, regardless of the ANSI_NULLS database option or the setting of SET ANSI_NULLS, use IS NULL and IS NOT NULL in comparisons that might contain null values."

https://docs.microsoft.com/en-us/sql/t-sql/statements/set-ansi-nulls-transact-sql

My takeaway is just to be sure to specify in every query what SQL Server should do when encountering a NULL. 

Friday, March 17, 2017

Twitter from C# .NET MVC..

 I wrote the Stock tracker application as mostly just a learning drill for Angular 1 and 2.
But I have found, it could be actually useful, with a touch of Twitter.

I find myself searching Twitter for charting,analysis, links, and just plain prognosticating by people, by searching the stock symbol preceded by "$".

So I set out to add some Twitter links to my site today.
There was a ton of helpful documentation by others already on the internet. Problem was the format and varying usage needs. I really needed to get the grunt work done in C# .NET. Some of the Gurus are using Node.js and other things, and some others were just making it harder than it needed for me to be.

What I wanted to end up with, and now have looks like this :




So the user can click the bird and see what has been tweeted about a company stock.

I would offer up that the heavy lifting is done by getting an OAuth token from Twitter first, and post that code for reference sake :

public string GetAuthToken()
        {

            string encodedKeyAndSecret = Convert.ToBase64String(
                new System.Text.UTF8Encoding().GetBytes(
                  ConsumerKey + ":" + ConsumerSecret));

            string urlToken = "https://api.twitter.com/oauth2/token";

            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri(urlToken));

            req.Headers.Add("Authorization","Basic " + encodedKeyAndSecret);
              
            req.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
            req.Method = "POST";

            string requestBody = "grant_type=client_credentials";

            Stream dataStream = req.GetRequestStream();

            byte[] byteArray = new System.Text.UTF8Encoding().GetBytes(requestBody);
            dataStream.Write(byteArray, 0, byteArray.Length);
            dataStream.Close();

            WebResponse response = req.GetResponse();

            string jsonResponse = "";

            using (var reader = new StreamReader(response.GetResponseStream()))
            {
               jsonResponse = reader.ReadToEnd();
            }

            TokenResponse t = 
                Newtonsoft.Json.JsonConvert.DeserializeObject(jsonResponse);
      
            return t.access_token;

        }

The rest of the act of querying Twitter is super easy to code, but can take some to get working, deserializing to something useable in your app, and then modeled by your classes and client-side objects.


Tuesday, March 07, 2017

ASP .NET MVC Angular 2, what I have so far..

So I set out to upgrade the first project of this kind which was written using Angular 1. My first few attempts with downloadable samples, were of the new MVC "wwwroot" Core format. These either did not run for me, or I could not see how to learn what I needed to learn using them.

So I had to start humble and create a traditional ASP .NET project and add MVC functionality to it. I figured this would be a good way to be able to download and learn from the quickstart at :

https://angular.io/docs/ts/latest/cookbook/visual-studio-2015.html

I got some help from the internet with adding MVC "areas" to my project, so as to add MVC functionality, but not lose the traditional ASP application behaviors.

I don't remember where exactly I picked up the "AREAS" education, but the topic is heavily explained :
https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=how+to+add+MVC+areas&*

Jumping to the end, my new stock tracker application runs at :
https://www.customconfiguration.net/ng/Stocks/StocksAngular


New users would want to be logged into Twitter for easy signup, or create a new account to use the app.

Forgiving my choice of traditional (Non-WebApi) controllers for a second, it only seems useful to look at a couple of sample methods :

   
     [Authorize()]
        [AcceptVerbs(HttpVerbs.Get)]
        public JsonResult ReadStockQuote(string Id)
        {
            Quote q;

            if (Id.Length > 0)
            {
                q = getQuoteFromYahoo(Id);
            }
            else
            {
                q = new Quote();
            }

            return Json(q, JsonRequestBehavior.AllowGet);
        }


        [Authorize()]
        [AcceptVerbs(HttpVerbs.Get)]
        [OutputCache(Duration = 360, VaryByParam = "Id")]
        public JsonResult ReadPrice(string Id)
        {
            string lastTrade = string.Empty;
            string color = string.Empty;
            string error = string.Empty;

            try
            {
                Quote q = getQuoteFromYahoo(Id);

                lastTrade = "$" + q.LastTrade;
                if (q.PercentChange.Contains("-"))
                {
                    color = "Red";
                }
                else
                {
                    color = "Green";
                }

            }
            catch (System.Exception exception)
            {
                lastTrade = "error";
                error = exception.Message;
            }

            return Json(
             new Stock()
             {
                 Price = lastTrade,
                 Color = color,
                 //                Message = error,
                 Symbol = Id
             },
             JsonRequestBehavior.AllowGet);
        }

So the code for the controllers just does basic Add, Delete, Get, And some RSS feed reading from Yahoo's generous web services.

Following the example provided at the Angular website, I created and "app" folder with "components" and "services" folders included> Everything prescribed in the tutorial, I copied into my project unchanged.

I then built the app I wanted following the example where relevant. I jumped around to get ahead to Routing, because I really wanted that feature even though my small app could certainly have existed in one view.

My main view for the single page app, just has one selector tag, along with the referenced scripts in the head tag :
 <base href="@Url.Content("~")">

 @Styles.Render("~/Content/css")

         <!-- Polyfill(s) for older browsers -->
         <script src="~/node_modules/core-js/client/shim.min.js"></script>
             <script src="~/node_modules/zone.js/dist/zone.js"></script>
 <script src="~/node_modules/systemjs/dist/system.src.js"></script>
 <script src="~/systemjs.config.js"></script>
 <script>

 System.
import('main.js')
.
catch(function (err) { console.
error(err); });

</script>



<stocks>Loading AppComponent content here </stocks>



The app Module script file has been updated from the example with my components :

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule } from '@angular/http';
import { Http } from '@angular/http';
import { Router } from '@angular/router';

import { AppServiceStocks } from './services/app.service.stocks';
import { AppStocks } from './components/app.component.stocks';
import { AddComponent } from './components/app.component.stockadd';
import { RemComponent } from './components/app.component.stockrem';
import { NewsComponent } from './components/app.component.stocknews';
import { AppComponent } from './components/app.component';

import { PageNotFoundComponent } from './components/not-found.component';
import { AppRoutingModule } from './app-routing';

@NgModule({
    imports: [BrowserModule, HttpModule, FormsModule, AppRoutingModule],
    declarations: [
        AppComponent,
        AppStocks,
        AddComponent,
        PageNotFoundComponent,
        RemComponent,
        NewsComponent],
    bootstrap: [AppComponent]
})
export class AppModule {

    // Diagnostic only: inspect router configuration
    constructor(router: Router) {
       // console.log('Routes: ', JSON.stringify(router.config, undefined, 2));
    }
}



The app Routing script file also was built from tutorial and just fleshed out with my own paths :


import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './components/app.component';
import { AppStocks } from './components/app.component.stocks';
import { AddComponent } from './components/app.component.stockadd';
import { RemComponent } from './components/app.component.stockrem';
import { NewsComponent } from './components/app.component.stocknews';

import { PageNotFoundComponent } from './components/not-found.component';

// import { CanDeactivateGuard } from './can-deactivate-guard.service';
// import { AuthGuard } from './auth-guard.service';

import { SelectivePreloadingStrategy } from './components/selective-preloading-strategy';

const appRoutes: Routes = [
    {
        path: 'Stocks/StocksAngular/Add',
        component: AddComponent
    },
    {
        path: 'Stocks/StocksAngular/Delete/:id',
        component: RemComponent
    },
    {
        path: 'Stocks/StocksAngular/News/:id',
        component: NewsComponent
    },
    {
        path: 'Stocks/StocksAngular',
        component: AppStocks
    },    
    { path: '', redirectTo: 'Stocks/StocksAngular', pathMatch: 'full' },
    { path: '**', component: PageNotFoundComponent }
];

@NgModule({
    imports: [
        RouterModule.forRoot(
            appRoutes,
            { preloadingStrategy: SelectivePreloadingStrategy }
        )
    ],
    exports: [
        RouterModule
    ],
    providers: [
   //     CanDeactivateGuard,
        SelectivePreloadingStrategy
    ]
})
export class AppRoutingModule { }


It is important to remember to not allow MVC to interfere with Angular's routing :
In the RouteConfig file place something like -

  routes.MapRoute(
              name: "ngOverride",
              url: "Stocks/StocksAngular/{*.}",
              defaults: new { controller = "Stocks", action = "StocksAngular" }
            );


The app service script file is called app.service.stocks.js :

import { Injectable } from '@angular/core';
import { Http, Response, URLSearchParams, RequestOptions, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import { stock } from '../components/stock';
import { rssitem } from '../components/rssitem';
import { quote } from '../components/quote';

@Injectable()
export class AppServiceStocks {

    private _getStocksListUrl = 'Stocks/StocksJSON';
    private _getStockDetailUrl = "Stocks/ReadStockQuote?Id=";
    private _getPriceUrl = "Stocks/ReadPrice?Id=";
    private _getNameUrl = "Stocks/GetNameFromSymbol?Id=";
    private _getNewsUrl = "http://feeds.finance.yahoo.com/rss/2.0/headline?s=";

    private _deleteStockUrl = "Stocks/Remove";
    private _addStockUrl = "Stocks/AddJSON";

    private _stockslist: stock[];

    constructor(private http: Http) {
    }

    stockslist(): Observable {
        return this.http.get(this._getStocksListUrl)
            .map(this.extractData)
            .catch(this.handleError);
    }

    stockDetail(symbol : string): Observable {
        return this.http.get(this._getStockDetailUrl + symbol)
            .map(this.extractData)
            .catch(this.handleError);
    }

    getNameFromSymbol(symbol: string): Observable {
        return this.http.get(this._getNameUrl + symbol)
            .map(this.extractData)
            .catch(this.handleError);
    }

    readPrice(symbol: string): Observable {
        return this.http.get(this._getPriceUrl + symbol)
            .map(this.extractData)
            .catch(this.handleError);
    }

    readNews(symbol: string): Observable {
        var newsLink = this._getNewsUrl
            + symbol
            + "&region=US&lang=en-US";

        let params: URLSearchParams = new URLSearchParams();
        params.set('Link', newsLink);
    
        return this.http.get("Stocks/ReadNewsData?",
            { search: params })
            .map(this.extractData)
            .catch(this.handleError);
    }

    readYahooNews(symbol: string): Observable {
       
        let params: URLSearchParams = new URLSearchParams();
        params.set('Symbol', symbol);

        return this.http.get("Stocks/ReadYahooNewsData",
            { search: params })
            .map(this.extractData)
            .catch(this.handleError);
    }

    remove(s : stock) : Observable {
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });

        return this.http.post(this._deleteStockUrl, { item : s }, options)
            .map(this.extractData)
            .catch(this.handleError);
    }

    add(s: string): Observable {
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });

        return this.http.post(this._addStockUrl, { Symbol : s }, options)
            .map(this.extractData)
            .catch(this.handleError);
    }

    private handleError(error: Response | any) {
        // In a real world app, we might use a remote logging infrastructure
        let errMsg: string;
        if (error instanceof Response) {
            const body = error.json() || '';
            const err = body.error || JSON.stringify(body);
            errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
        } else {
            errMsg = error.message ? error.message : error.toString();
        }
        console.error(errMsg);
        return Observable.throw(errMsg);
    }

    private extractData(res: Response) {
        let body = res.json();
        return body || {};
    }
The main controller, the Stocks list controller, which rotates down the list getting the current prices from Yahoo : I think this came out cleaner to code than the Angular 1 version of it.
import { Component } from '@angular/core';
import { stock } from './stock';
import { quote } from './quote';
import { Observable } from 'rxjs/Observable';
import { AppServiceStocks } from '../services/app.service.stocks';

@Component({
    selector: 'stocks',
    templateUrl: './app/components/app.component.stocks.html?v=4',
    providers: [AppServiceStocks]
})

export class AppStocks {

    name = 'Angular Stocks';
    stockslist: stock[];
    mode = 'Observable';
    statusMessage = "";
    newStockName = "";
    newStockSym = "";
    newStock = new stock();
    rownum = 0;
    interval = 2;
    
    constructor(private _appService: AppServiceStocks) {
        
    }

    ngOnInit() {
        this.newStockName = "";
        this.getStocks();
    }

    private readPrice() {
        this._appService.readPrice(this.stockslist[this.rownum].Symbol)
            .subscribe(result => {
                this.stockslist[this.rownum].Price = result.Price;
                this.stockslist[this.rownum].Color = result.Color;
                this.rownum++;
                if (this.rownum == this.stockslist.length) {
                    this.rownum = 0;
                }
                this.getNextQuote();
            });
    }

    private getNextQuote() {
      setTimeout(() => { this.readPrice() }, this.interval * 1000);
    }

    getStocks() {
        this._appService.stockslist()
            .subscribe(
            stocks =>
            {
                this.stockslist = stocks;
                this.getNextQuote();
            });
    }

    

}

Saturday, October 29, 2016

Migrating Asp .Net MVC to .Net Core .. Authentication

Hi all, long time no post...

I am learning the new .NET CORE project type now, and want to rewrite my application from the old ASP .NET MVC in the new project template. Reasons for this for me, are really just that my old project is AngularJS 1.0, and I want to try to rebuild using Angular 2.0 with Typescript.

So one stumbling block I hit was the Identity and Authentication database from the old project would not work with the new project. It gave some errors about certain columns missing. What I have had some partial success with is a migration script to update the old DB. This has allowed me to login using a user name and password, and to create a new user. That is the extent of my testing :


Alter Table ASPNETROLES
ADD
 ConcurrencyStamp varchar(255) null,              
 NormalizedName varchar(255) null

 Drop Table AspNetUserTokens

 CREATE TABLE [AspNetUserTokens] (
    [UserId]        NVARCHAR (450) NOT NULL,
    [LoginProvider] NVARCHAR (450) NOT NULL,
    [Name]          NVARCHAR (450) NOT NULL,
    [Value]         NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_AspNetUserTokens]
PRIMARY KEY CLUSTERED ([UserId] ASC, [LoginProvider] ASC, [Name] ASC)
)

Alter Table AspNetUsers
 Add
 ConcurrencyStamp varchar(255) null,
 LockoutEnd DateTime null,
 NormalizedEmail varchar(255) null,
 NormalizedUserName varchar(255) null

Drop Table [AspNetRoleClaims]

CREATE TABLE [AspNetRoleClaims] (
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [ClaimType]  NVARCHAR (MAX) NULL,
    [ClaimValue] NVARCHAR (MAX) NULL,
    [RoleId]     NVARCHAR (128) NOT NULL,
    CONSTRAINT [PK_AspNetRoleClaims]
PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId]
FOREIGN KEY ([RoleId])
REFERENCES [dbo].[AspNetRoles] ([Id]) ON DELETE CASCADE
)


GO
CREATE NONCLUSTERED INDEX [IX_AspNetRoleClaims_RoleId]
    ON [AspNetRoleClaims]([RoleId] ASC)

Alter Table AspNetUserLogins
   Add  ProviderDisplayName varchar(255) null





You may find this discussion relevant if you have gotten this error :
SqlException: Invalid column name 'NormalizedUserName'. Invalid column name 'ConcurrencyStamp'. Invalid column name 'LockoutEnd'. Invalid column name 'NormalizedEmail'. Invalid column name 'NormalizedUserName'.

Monday, June 02, 2014

ASP. Net site with many Update Panels?

So you have an ASP. Net site with many Ajax Update Panels? The problem : trying to do client side javascript interactions when the page keeps posting back partial. The scripts need to run after the partial page returns, so they are not overridden by the back end.
The solution:
Capture the page upon return with the following:
            var sysApplication = Sys.WebForms.PageRequestManager.getInstance();
            sysApplication.add_initializeRequest(beginRequest);
            sysApplication.add_endRequest(endRequest);
 
            function beginRequest() {
               // document.getElementById("divMessage").style.display = "inline";
              
            }
 
            function endRequest() {
                setWizardProgress();
            }