Archive

Archive for the ‘MVC’ Category

AWS Cognito error for certain zones?

I was trying to manage via CSharp code the signup on Cognito.
I created an IAM user with Administrator rights, and obtained the key and secret for a Cognito pool, using the eu-west3 zone (Paris).
The code is simple:

[HttpPost]
public async Task<IActionResult> Signup(SignupModel model)
{
    if (ModelState.IsValid)
    {
        var user = _pool.GetUser(model.Email);
        if (user.Status != null)
        {
            ModelState.AddModelError("UserExists", "User with this email already exists");
            return View(model);
        }
        var createdUser = await _userManager.CreateAsync(user, model.Password).ConfigureAwait(false);
        if (createdUser.Succeeded) RedirectToAction("Confirm");
    }
    return View(model);
}

_pool is an injection in the controller constructor of CognitoUserPool, _userManager of UserManager<CognitoUser>.
I created a simple MVC .NET Core 5 web app, a View with a submit Button to the above method.
At the POST attempt, I got the error that cognito-idp.eu-west3.amazonaws.com is not reachable.
I controlled everything, and searched for the error on Google… nothing.
So I tried to create another Cognito pool in another region, eu-central-1 (Frankfurt)
Changed in appsettings.json Region, UserPoolClientId, UserPoolClientSecret, UserPoolId: POST on controller working!
Because I don’t use the Hosted UI in this second pool? This needs to be investigated.

Categories: .NET Core, AWS, MVC, Vs2022

Azure Feature

First, we need to create in Azure portal an App Configuration

We see

Step 2, we create a Resource named in this sample TestDigest

Click on new resource, we see a panel where we find

Here we click Add, and we add a feature named FeatureA:

As

We have

In the secrets.json of out project we have ConnectionStrings:AppConfig, which stores the connection string for your App Configuration store:

{
   "ConnectionStrings": {
      "AppConfig": "Endpoint=https://testdigest.azconfig.io;Id=…."
   }
}

You can find the connection string under Access Keys in the Azure portal.
Replace the AppConfig placeholder with your App Configuration store’s connection string.

At this point we need to create an little sample MVC .Net Core app named TestFeatureFlags.
In this app we need the Nuget packages Microsoft.Azure.AppConfiguration.AspNetCore and Microsoft.FeatureManagement.AspNetCore.
Then we add this class:

namespace TestFeatureFlags
{
    public static class MyFeatureFlags
    {
        public const string FeatureA = "FeatureA";
    }
}

In Startup.cs Configure

app.UseAzureAppConfiguration();

Before app.UseEndPoint
In ConfigureServices

services.AddFeatureManagement();
services.AddAzureAppConfiguration();

In Program.cs we have the Feature configuration:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    webBuilder.ConfigureAppConfiguration(config =>
    {
        var settings = config.Build();
        config.AddAzureAppConfiguration(options =>
            options.Connect(settings["ConnectionStrings:AppConfig"]).UseFeatureFlags(featureFlagOptions => {
                featureFlagOptions.CacheExpirationInterval = TimeSpan.FromSeconds(5);
            }));
    }).UseStartup<Startup>());

Note that for test purpose the default of 30s is changed to 5s.
When you change the Feature on/off on the portal, it will pass the expiration interval before in code is acknowledged the change.
In this case for test i changed the code in Privacy method in the Home controller:

public async Task<IActionResult> Privacy()
{
    if (await _featureManager.IsEnabledAsync(MyFeatureFlags.FeatureA))
    {
        Console.WriteLine("FeatureA activated");
    }
    else
    {
        Console.WriteLine("FeatureA NOT activated");
    }
    return View();
}

Then with feature disabled by clicking on Privacy link:

We can enable the feature:


And then after 5 seconds by a new click on Privacy:

Categories: .NET Core, Azure, DevOps, MVC

Fake Node.js MVC web server using Ubuntu 17.10

Sometimes is needed a simple MVC web test server, and I was needing it in an Ubuntu environment.
Immediately i start thinking about Node.js and discovered the Hapi package.
This is a package for building applications and services, the sample in home page is good for starting.
Interesting also Hoek, a general purpose node utilities useful for arrays and objects, found also this documentation.
In order to have a zero-dependency module that loads environment variables from a .env file into process.env i used dotenv.
The use I simple, I created a directory , launched an npm init and the create a package.json file as

{
  "name": "nodejsfakewebsvr",
  "version": "1.0.0",
  "main": "nodeFakeWebSvrMvc.js",
  "dependencies": {
    "dotenv": "^5.0.1",
    "hapi": "^17.3.1",
    "hoek": "^5.0.3"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": ""
}

For future experiments with a real database, i created a settings.js as:

// This will load our .env file and add the values to process.env,
// IMPORTANT: Omit this line if you don't want to use this functionality
require('dotenv').config({silent: true});

module.exports = {
  port: process.env.PORT || 8001,
  env: process.env.ENV || 'development',

  // Environment-dependent settings
  development: {
    db: {
      dialect: 'sqlite',
      storage: ':memory:'
    }
  },
  production: {
    db: {
      dialect: 'sqlite',
      storage: 'db/database.sqlite'
    }
  }
};

 Then i launched from shell in the folder project:

sudo npm install --save hapi hoek dotenv

 The main javascript code, nodeFakeWebSvrMvc.js:

'use strict';

const Hapi = require('hapi');
const Hoek = require('hoek');
const Settings = require('./settings');

const server=Hapi.server({
    host:'localhost',
    port:Settings.port
});

server.route({
    method:'GET',
    path:'/hello',
    handler:function(request,h) {
        return 'hello world';
    }
});

server.route({
    method:'GET',
    path:'/api/IntegrationData/2/DequeueRabbit',
    handler:function(request,h) {
        return 'GET dequeued';
    }
});

server.route({
    method:'POST',
    path:'/api/IntegrationData/2/DequeueRabbit',
    handler:function(request,h) {
        return 'POST dequeued';
    }
});

// Start the server
async function start() {
    try {
        await server.start();
    }
    catch (err) {
        console.log(err);
        process.exit(1);
    }
    console.log('Server running at:', server.info.uri);
};

start();

 Now the code can be run using for example

node /home/alessi/node/fakeWebSvrMvc/nodeFakeWebSvrMvc.js

 In Visual Studio Code at runtime we can see something as

Categories: MVC, Node, Ubuntu

View component rendered from a Controller

In .Net Core we have the View components.
Typically the syntax is

@await Component.InvokeAsync("ControlBar")

In a .cshtml page.
These components can be inserted into a library; how to use them if the project is a SPA, that uses Web API controllers?
You can create a method in a Web API controller as

[HttpGet("[action]")]
public IActionResult ControlBar()
{
    return ViewComponent("ControlBar");
}

In order to work there must be a reference to Microsoft.AspNetCore.Mvc.ViewFeatures, where the ViewComponent is a ViewComponentResult; the result is html with javascript if present.

Categories: .NET Core, MVC

WebAPI2 errors management in Android

In my last project is used an SQL Server 2014 Phones table, with an Android app that register the device itself in this table via an WebAPI2 call.

The table is simple:


We have an primarykey named “PK_Phones” on PhoneId.

The PhoneID is the IMEI code, if the device has phone capabilities and is installed an working SIM, or the Android ID.

There are other common fields for multiuser management as the timestamp which is very useful with the Entity Framework 6 used in this solution.

The Web counterpart (created with Visual Studio 2013) is a common MVC5 project using WebAPI2 with Breeze, Angular , Bootstrap, Modernizr, Moment, Ninject, Toastr…an huge blob installed from NuGet using the HotTowel package (Ninject , Jasmine for testing and other package needs to be installed by hand).

The first move was to create an ADO.NET Entity Data Model (here i don’t speak in detail of the project organization) and using “Code first from Database” i created the class representing my table:

namespace net.studioalessi.walkad.dl
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;

    public partial class Phones
    {
        [Key]
        [StringLength(20)]
        public string PhoneId { get; set; }

        [StringLength(30)]
        public string Manufacturer { get; set; }

        [StringLength(20)]
        public string Model { get; set; }

        [StringLength(150)]
        public string Notes { get; set; }

        [StringLength(20)]
        public string PhoneNumber { get; set; }

        public bool Banned { get; set; }

        [StringLength(50)]
        public string UtenteModifica { get; set; }

        public DateTime? OraModifica { get; set; }

        [StringLength(50)]
        public string UtenteInserimento { get; set; }

        public DateTime? OraInserimento { get; set; }

        [Column(TypeName = "timestamp")]
        [MaxLength(8)]
        [Timestamp]
        public byte[] SSMATimeStamp { get; set; }
    }
}

Then the necessary Controller (right click on Controllers folder->add new scaffolded item->


Web API 2 Controller with actions, using Entity Framework as in figure.

Using the previous Phones class we generate the Controller and the Context, in our case the context is the class PhonesContext:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace net.studioalessi.walkad.dl
{
    public class PhonesContext : DbContext
    {
        public PhonesContext()
            : base("name=WalkadConn")
        {
        }

        // no virtual ? in the previous EF version was generated as virtual...
        public DbSet<Phones> Phones { get; set; }

        // Also the code below was generated automatically in the previous version.
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Phones>()
                .Property(e => e.UtenteModifica)
                .IsUnicode(false);

            modelBuilder.Entity<Phones>()
                .Property(e => e.UtenteInserimento)
                .IsUnicode(false);

            modelBuilder.Entity<Phones>()
                .Property(e => e.SSMATimeStamp)
                .IsFixedLength();
        }
    }
}

And the Controller is changed from the generated version to an BreezeController

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using Walkad.Models;
using net.studioalessi.walkad.dl;
using Breeze.WebApi2;

namespace Walkad.Controllers
{
    [BreezeController]
    public class PhonesController : ApiController
    {
        private IPhoneRepository _repo;

        public PhonesController(IPhoneRepository repo)
        {
            _repo = repo;
        }

        [HttpGet]
        public string Metadata()
        {
            return _repo.MetaData;
        }

        [HttpPost]
        [AllowAnonymous]
        public async Task<IHttpActionResult> RegisterImei(PhoneModel model)
        {
            PhonesContext objT = new PhonesContext();
            try
            {
                Phones objTrace = new Phones
                {
                    UtenteInserimento = "RemoteUser",
                    OraInserimento = DateTime.Now,
                    Model = model.Model,
                    Manufacturer = model.Manufacturer,
                    PhoneId = model.Imei
                };
                //
                objT.Phones.Add(objTrace);
                await objT.SaveChangesAsync();
                return Ok();
            }
            catch (Exception ex)
            {
                return BadRequest(ex.ToString());
            }
            finally
            {
                //
            }
        }
    }
}

The PhoneModel was created by hand and is the model representing the JSON string sent from the client (Web site or Android app, as in this case)

public class PhoneModel
{
    [Required]
    [StringLength(15)]
    public string Imei { get; set; }

    [StringLength(30)]
    public string Manufacturer { get; set; }

    [StringLength(20)]
    public string Model { get; set; }
}

At this point we can try the service from Fiddler


By clicking “Execute” we can see that our call was successful


And in SQL Management Studio we can see the new record:


Ok , we can create the Android app with Eclipse Luna (with the Android Developer Tools).

I don’t show the details, i created an simple Android app with an menu item that calls the routine named registerThisDevice:

private void registerThisDevice() {
    SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    String strBasePath = sharedPrefs.getString((String)getText(R.string.pref_httpbase), "http://192.168.0.77/Walkad");
    new WebApiRegisterImei().execute(strBasePath + "/api/Phones/RegisterImei");
}

Note the i’m using the IP , the tablet used for the test is in the same WiFi network but is not Windows, is not able to resolve the NETBIOS name.

Note also that we web address is registered in a Preference that defaults to my WiFi address.

In order to follow the best practices , the routine WebApiRegisterImei is an AsyncTask so is called with .execute() passing the server address as String argument (in every case , if we don’t use an AsyncTask we get an NetworkOnMainThread exception):

private class WebApiRegisterImei extends AsyncTask<String, Void, JSONObject> {
    ProgressDialog myProgressBar;
    public String ExportResult = "";
    public String LastException = "";
    public Exception LastExceptionObject;       

    protected JSONObject doInBackground(String... params) {
        BufferedInputStream  in = null;
        HttpURLConnection urlConnection = null;
        try {
            URL url = new URL(params[0]);
            urlConnection = (HttpURLConnection) url.openConnection();
            // the Android docs says that HttpURLConnection uses the GET method by default. It will use
            // POST if setDoOutput(true) has been called. Other HTTP methods
            // (OPTIONS, HEAD, PUT, DELETE and TRACE) can be used with
            // setRequestMethod(String). Anyway forcing POST as request method is useful.
            urlConnection.setRequestMethod("POST");
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(true);
            //
            JSONStringer vm;
            vm = new JSONStringer()
                    .object().key("Imei").value(MainActivity.ImeiId)
                             .key("Manufacturer").value(android.os.Build.MANUFACTURER)
                             .key("Model").value(android.os.Build.MODEL)
                    .endObject();

            urlConnection.setFixedLengthStreamingMode(vm.toString().getBytes().length);
            urlConnection.setRequestProperty("Content-Type", "application/json");
            urlConnection.setRequestProperty("charset", "utf-8");
            DataOutputStream wr = new DataOutputStream (
                    urlConnection.getOutputStream ());
                    wr.writeBytes (vm.toString());
                    wr.flush ();
                    wr.close ();                           
            //
            int statusCode = urlConnection.getResponseCode();
            if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
                // handle unauthorized (if service requires user login)
            } else if (statusCode != HttpURLConnection.HTTP_OK) {
                InputStream errorstream = urlConnection.getErrorStream();
                BufferedReader br = null;
                if (errorstream == null){
                    InputStream inputstream = urlConnection.getInputStream();
                    br = new BufferedReader(new InputStreamReader(inputstream));
                }else{
                    br = new BufferedReader(new InputStreamReader(errorstream));
                }
                String strResponse = "";
                String strTemp;
                while ((strTemp = br.readLine()) != null){
                    strResponse += strTemp;
                }   
                strResponse = strResponse.toLowerCase();
                if(strResponse.contains("pk_phones"))
                {
                    ExportResult = getErrorDuplicated();
                    return null;
                }
            }
            //
            ExportResult = getExportCompleted();
        } catch (Exception e) {
            Mint.logException(e);
            LastException = e.toString();
            LastExceptionObject = e;                
        } finally {
            if (in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    Mint.logException(e);
                }
            }
            if(urlConnection != null){
                urlConnection.disconnect();  
            }
        }
        return null;
    }

    private String getErrorDuplicated() {
        return (String)getText(R.string.imeiduplicatederr);
    }

    private String getExportCompleted(){
        return (String)getText(R.string.registerimeidone);
    }           

    @Override
    protected void onPreExecute() {
        try {
            myProgressBar = new ProgressDialog(MainActivity.thisMainActivity, ProgressDialog.STYLE_SPINNER);    
            myProgressBar.setMessage((String)getText(R.string.registerimeiwait));
            myProgressBar.show();
        } catch (Exception e) {
            Mint.logException(e);
            Toast.makeText(getBaseContext(), (String)getText(R.string.genericerror),Toast.LENGTH_LONG).show();                  
        }
    }

    @Override
    protected void onPostExecute(JSONObject result) {
        try {
            myProgressBar.dismiss();
            if(LastException.length() > 0){
                Toast.makeText(getBaseContext(), (String)getText(R.string.genericerror),Toast.LENGTH_LONG).show();   
                Mint.logException(LastExceptionObject);
            }else{
                Toast.makeText(MainActivity.thisMainActivity, ExportResult, Toast.LENGTH_LONG).show();
            }
        } catch (Exception e) {
            Mint.logException(e);
            Toast.makeText(getBaseContext(), (String)getText(R.string.genericerror),Toast.LENGTH_LONG).show();                  
        }
    }
}

Examining the code , we can note that onPreExecute and onPostExecute are the only places where we can interact with the main thread and relative controls (the progress bar) so in the background part we register the exceptions details in one string and one object, examining them on the onPostExecute.

Note the use of the JSONStringer: debugging the code you can see that is created the JSON string as used in Fiddler.

If all is ok, we can see that in SQL Server is written a new row in the Phones table.

The test for statusCode != HttpURLConnection.HTTP_OK was the difficult part to discover how it works, the subject of this post.

If you reissue the command from the Android app on the same device you violate the primary key, so the WebAPI code goes in the Exception and is sent to the client “BadRequest” (HTTP error 400) instead of “Ok”(200), sending as other info ex.ToString().

The management of the returned error is done as you can read in the code: we need to examine the returned stream , by reading this stream we can create a string that contains the original ex.ToString, for example

{"$id":"1","$type":"System.Web.Http.HttpError, System.Web.Http","Message":"System.Data.Entity.Infrastructure.DbUpdateException: 
An error occurred while updating the entries. See the inner exception for details. ---> 
System.Data.Entity.Core.UpdateException: An error occurred while updating the entries. 
See the inner exception for details. ---> System.Data.SqlClient.SqlException: Violation of PRIMARY KEY constraint 'PK_Phones'. 
Cannot insert duplicate key in object 'dbo.Phones'. The duplicate key value is…..

So in the Android code we search if the string contains the name of the primary key , giving to the user an scoped message, or we give to the user an generic message and the code uses Splunk Mint Express for registering all infos about the unmanaged exception (that could be an network failure, and so on).

Breeze SaveChanges issue with Ie10

Every new technology is an hard path , an high mountain to climb.

I’m working to an MVC project with ASP.NET 4.5 , Breeze and Angular, authentication done with the new ASP.NET Identity, Entity Framework 6.

The big problem , for the average developer (me) , is:

  • For Microsoft, on MSDN you can find a lot of verbose pages about the syntax: an simple CRUD example using these technologies , add update delete of records, is hard to find.
  • For thirdy parties technologies you can find abstruse , overcomplexed samples ; but still there is a lack of a simple table management with CRUD operations.

In every case the samples simply ignore (at the least the ones that i discover with Google) the everyday questions with concurrency : another user could have deleted the record that you are trying to save, or you are trying to save a record of a table where there is an unique index for a field.

Ok , this was the rant, now the problem.

The code is about the update of a record in a Vehicle Classes table (ClassiMezzi).

Using Breeze, in a BreezeController (a class decorated with “[BreezeController]”) the typical sample for an update is

[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
    return _repo.SaveChanges(saveBundle);
}

This works with Ie11, the latest Firefox, Chrome, Safari; _repo is an repository object that at the end calls the Entity Framework context provider (in our sample EFContextProvider<ClassiMezziContext>)

But if you try to use this method for adding a new record on a table where there is an index UNIQUE, there are troubles with the error messages using Internet Explorer 10.

For these new apps i simply don’t consider Ie < 10; if you are still using Ie6 , Ie7.. time to upgrade …but to ignore the previous version is a bit excessive, for me: i test everything with the latest Firefox, Chrome also under Linux Android and Mac, for Ie to test the latest version and the previous seems reasonable.

And so, testing the code with multiuser issues (record deleted from another user, check constraints, UNIQUE indexes) i discovered the problem.

From the javascript side , the controller is called with an Ajax call with callbacks for success or failure:

function saveFailed(error) {
    try {
        manager.detachEntity(service.ClasseMezzoEntity);
    }
    catch (ex) { }
    service.ClasseMezzoEntity = null;
    if (error.httpResponse.status === 401 || error.httpResponse.status === 403) {
        logError(localize.localizeText("UnauthorizedOperation"), null, true);
        return;
    }
    //var strMsg = breeze.saveErrorMessageService.getErrorMessage(error).toLowerCase(); old version ok with Ie11 , not Ie10
    var strMsg = error.httpResponse.data.Errors[0].toLowerCase();
    if (strMsg.indexOf("entities may have been modified or deleted since entities were loaded") > 0) {
        logWarning(localize.localizeText("AnotherUsrHasChangeData"), null, true);
        return;
    }
    if (strMsg.indexOf("ix_classimezzi") > 0) {
        logWarning(localize.localizeText("ClassNameDuplicated"), null, true);
        return;
    }
    if (strMsg.indexOf("errdelete:anotheruser") > 0) {
        logWarning(localize.localizeText("AnotherUsrHasDeleteData"), null, true);
        return;
    }
    //  other code for unknown error management
    var msg = breeze.saveErrorMessageService.getErrorMessage(error);
    // etc.
};
 
function saveClasseMezzo() {
    service.isSaving = true;// see at the end of this module
    service.lastSaveSuccess = true;
    return manager.saveChanges()
        .catch(saveFailed)
        .then(saveClasseMezzoAttemptDone);
};

For example we try to add another ClasseMezzo (Vehicle class) with the same name of an existing item on the the db table.

With Ie11 and other latest browsers the first controller code is ok , and if our ajax call fails because the UNIQUE index the first

var strMsg = breeze…. commented code works perfectly.

But while with Ie11 etc. the error.message in javascript contains the string “Cannot insert duplicate key row …” with the index name that helps to manage the error (in this sample the index name is “ix_classimezzi”) in Ie10 we get


That is an anonymous “An error has occurred” that can’t help to manage the error, and no trace of the original SQL error in httpResponse: a big trouble if i would to say to the customer “you are trying to insert an duplicate item” because could be another thing , for example an SQL check violation.

The problem is the simple approach in the WebApi2 controller.

In order to manage correctly the errors coming from multiuser issues the WebAPI2 controller code must be:

[HttpPost]
[Authorize(Roles = "PowerUser")]
public SaveResult SaveChanges(JObject saveBundle)
{
    SaveResult objRes = null;
    List<object> objErr = null;
    var classimezziList = JsonConvert.DeserializeObject<List<ClassiMezzi>>(saveBundle.SelectToken("entities").ToString());
    try
    {
        objRes = _repo.SaveChanges(saveBundle);
        return objRes;
    }
    catch (Exception ex)
    {
        var keyMappings = new List<KeyMapping>();
        objErr = new List<object> { JsonConvert.SerializeObject(ex.Message) };
        return new SaveResult()
        {
            Entities = classimezziList.Cast<object>().ToList(),
            Errors = objErr,
            KeyMappings = keyMappings
        };
    }
}

Let’s examine the code.

We pass to the controller an JSON object , that we want deserialized in order to correctly manage the error; note in the catch management code: is explicitly created an SaveResult object , instead of the automatic creation.

Setting the Errors field to something != null , in our case the error message serialization, we cause the call of the javascript failure callback , and in the javascript failure code the message must be read as

var strMsg = error.httpResponse.data.Errors[0].toLowerCase();

because when the code is falling back we have this , now:


So we can manage the things as in the next rows.

This is valid also for Ie11 , and Firefox, Chrome under Windows, Linux, Mac, Android.

At least, this is the situation with the current latest Breeze (1.3.8)

The requested resource does not support http method ‘POST’

After a life of web sites developed with Web Forms, now i’m working with MVC.

Not a basic MVC, but SPA apps using Angular, Breeze, Twitter Bootstrap.. an huge jump.

In the Controller folder of my solution i was thinking to create an utilities controller, in this case for the logging of javascript errors, paging utilities, and so on.

So i initially created a class as:

public class ServicesController : ApiController
{
	[HttpPost]
	public void SubmitError(string jsRespoText, string jsstatus, string jssource)
	{
		ExceptionUtility.LogJsExceptionFile(jsRespoText, jsstatus, jssource);
	}

	[HttpGet]
	public JsonGenResponse CreatePagerStdTable(int startRowIndex, int totalCount, int pageSize, string jsLoadDataFunName, string Filter, string QueryType)
	{
		// this will be another post…
	}
}

For the HttpGet , no problem.

When i tried from Fiddler the HttpPost method i got the error written in the subject of this post.

A Controller decorated with [BreezeController] works as POST, and it was tested.. but it uses a JObject parameter, i noticed.

The POST problem pops out when you try to use primitive (string, int..) parameters in a Web API call instead of an object.

A solution could be to use [FromBody], with this attribute WebAPI search the attribute:value in the request body.

But there is a problem : is not possible to specify more than one “[FromBody]” , so is not possible to write:

public void SubmitError([FromBody]string jsRespoText, [FromBody]string jsstatus, [FromBody]string jssource)

The solution is to create a class, in my case

public class JsErrorData
{
    public string jsRespoText { get; set; }
    public string jsstatus { get; set; }
    public string jssource { get; set; }

}

And then write the method as

[HttpPost]
public void SubmitError(JsErrorData jsObjErr)
{
    ExceptionUtility.LogJsExceptionFile(jsObjErr.jsRespoText, jsObjErr.jsstatus, jsObjErr.jssource);
}

The javascript caller:

function logJsError(responseText, status, source, callback) {
    var ajaxImpl = breeze.config.getAdapterInstance("ajax");
    ajaxImpl.ajax({
        type: "POST",
        contentType: "application/json;charset=UTF-8",
        url: strBaseHttp + "api/Services/SubmitError",
        data: "{'jsRespoText': '" + responseText.replace(/'/g, '') + "','jsstatus':'" + status + "','jssource':'" + source + "'}",
        success: function (html) {
            callback(html);
        },
        async: true
    });
}

Now HttpPost is working.

Categories: .NET, JQuery, MVC, SPA

OData services

My previous post is half the story.

The original intent was to use Entity Framework and build an WCF data service, but with the latest EF 6.1.0 it seems that is no more possible.

Microsoft is now strongly oriented to the new OData religion: i discovered that there is a NUGet project for a bridge between WCF and EF6 in a alpha stage, and it seems abandoned from months.

I tried, but it installs an EF6 prerelease, and in every case i was not able to obtain something of working.

In my previous post i was able to create an EF model, and then i added an WCF Data Service 5.6.

But it was not working , trying to launch the .svc file i got

“The server encountered an error processing the request. See server logs for more details.”

I ended up with this code:

namespace NorthwindWeb
{
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class NorthwindCustomers : DataService<NorthwindEntities1>
    {
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.UseVerboseErrors = true;
            config.SetEntitySetAccessRule("Customers", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
        }
    }
}

The ServiceBehavior decoration gives detailed error infos , which was:

“The server encountered an error processing the request. The exception message is ‘Expression of type ‘System.Data.Entity.Core.Objects.ObjectContext’ cannot be used for return type ‘System.Data.Objects.ObjectContext”

It seems that i should use EntityFrameworkDataService<NorthwindEntities1> instead of DataService, but this object is apparently not available in EF6 object model or somewhere else, and i’m not the best WCF/EF expert.

I’m thinking to the ones (also me) that had written a lot of WCF code using EF…they should rewrite all? or the projects needs to be “closed”, no more NuGet updates ?

This is another case where “googling” i find that i’m not alone.

Ok , waiting for the next Microsoft change of mind (“throw away OData: is old, unsecure, blah blah.. now there is <new buzzword>”) i wrote the OData web service with Web API…sometimes i envy the COBOL programmers that are maintaining ancient software where the screens are painted with | and -.

So from Visual Studio 2013 new project ->ASP.NET Web Application


Then in the next wizard step choose the Empty template but add Web API:


This is the moment of adding via NuGet (TOOLS->NuGet Package Manager->Manage NuGet packages for Solution.. the Entity Framework and OData.


 
 


Now is recommended to clic on Updates and update all of the NuGet components:


Now if we want to add an Data Model there is still the trouble described in the previous post; so copy the sample connection string in web.config (also from the mentioned previous post) changing the name , data source, initial catalog and so on.

We still use Northwind as sample, so we add to the solution an ADO .NET Entity Data Model that in our sample we call NorthwindModel.edmx

In order to avoid mistakes , is better to leave checked the creation of a new connection string, that will be the one used:


In this sample we still use only the Customers table


With a Model Namespace = NorthwindModel

Do a Build->Rebuild all, otherwise in the next step Visual Studio complains about an error.

In the automatically generated Controllers folder (in the Visual Studio solution) right clic and add a controller:


We add “Web API2 OData with actions, using Entity Framework”.

In the next step by simply clicking on the drop downs and writing “CustomersEntity” in Controller name we can set the controller as in figure:


In the Controllers folder now we have a CustomersEntityController.cs class: Visual Studio and his template engine is so gently that is generated also an sample of code:


The commented code must be copied in WebApiConfig.cs class under the App_Start folder, so at the end we have

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.OData.Builder;
using NorthwindSvc;
 
namespace NorthwindSvc
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
 
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Customers>("CustomersEntity");
            config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
        }
    }
}

Ok, now we can launch the site.

Tipically this does nothing, complaining that a default document is not configured for the requested URL, and directory browsing is not enabled on the server.

But in CustomersEntityController.cs we can see examples of calling the web service, so if we add /odata/CustomersEntity to the browser address (for example http://localhost:11775/odata/CustomersEntity)

we get an JSON file with the records of the Customers table:


This with Google Chrome, Internet Explorer requires to download the JSON file.

Observing the generated code we can read

// GET odata/CustomersEntity(5)
[Queryable]
public SingleResult<Customers> GetCustomers([FromODataUri] string key)
{
    return SingleResult.Create(db.Customers.Where(customers => customers.CustomerID == key));
}

This means that we can write /odata/CustomersEntity(‘ALFKI’) and we get only the corresponding record.

With OData are coming for free many interesting features regarding the querystring

For example we can write


That is we filter our data for Country = Mexico and we need only the company name and the fax; the result:


The condition could be complex, for example we can specify &filter=Country eq ‘Mexico’ and CustomerID eq ‘ANTON’

A reference of the OData query options at these addresses:

http://msdn.microsoft.com/en-us/library/gg312156.aspx

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options

Categories: .NET, MVC, OData, Vs2013

Upgrade of Durandal projects

I’m working with Durandal , and i need to upgrade some old (old..2013…) code.
But this code was written when there was Durandal 1.x , now with NuGet in a Visual Studio 2013 project the version is 2.01 , and are changed a lot of things.

Fortunately in this Durandal site page and in this Papa’s page there are useful guidelines.

Categories: .NET, Durandal, JQuery, MVC, SPA