JWT Authorization in .NET Core -- 401 Error - asp.net-mvc

I am trying to implement JWT authentication between a simple UI and Web API. Both are .NET Core 2.0 and I'm using Ajax to call the API functions. I am able to login with no problem whatsoever and it passes the Bearer token back to me; however, when I decorate my SaveProduct method with Authorize, call it with Ajax, and pass the token, it returns 401 Unauthorized. As soon as I remove the Authorize it works fine. I've worked for days to figure out what I'm missing and have created several iterations of my apps to see if that would help, but have not been able to figure it out. I've scoured the web and tried numerous suggestions, but as yet to no avail.
I would appreciate any insights you might have. Thanks in advance!
Here is my code:
WEB API -- STARTUP
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace SportsStoreAngAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.Build());
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superdooperultrasafeKey#999")),
RequireSignedTokens = false,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://localhost:3700",
ValidAudience = "http://localhost:3700"
};
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("AllowAllOrigins");
app.UseStatusCodePages();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
}
WEB API -- LOGIN CONTROLLER
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SportsStoreAngAPI.Models;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SportsStoreAngAPI.Controllers
{
[Route("[controller]")]
public class LoginController : Controller
{
[HttpPost]
public JsonResult Login([FromBody] LoginModel user)
{
LoginReturnModel l = new LoginReturnModel();
if (user == null)
{
l.Success = false;
l.Token = null;
}
if (user.Name == "admin" && user.Password == "secret")
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#345"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokenOptions = new JwtSecurityToken(
//issuer: "http://localhost:3700",
//audience: "http://localhost:3700",
//claims: new List<Claim>(),
expires: DateTime.Now.AddDays(5),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
l.Success = true;
l.Token = tokenString;
}
else
{
l.Success = false;
l.Token = null;
}
JsonResult jR = new JsonResult(l);
return jR;
}
}
}
WEB API - PRODUCTS CONTROLLER
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SportsStoreAngAPI.Models;
using System.Collections.Generic;
namespace SportsStoreAngAPI.Controllers
{
[Produces("application/json")]
[Route("[controller]")]
public class ProductsController : Controller
{
[HttpGet]
public IEnumerable<Product> GetProducts()
{
List<Product> p = new List<Product>();
return p;
}
[HttpPost, Authorize]
public Product SaveProduct([FromBody]Product p)
{
return p;
}
}
}
FRONT END UI
#{
ViewData["Title"] = "Home Page";
}
<button class="btn btn-primary" type="button" onclick="loginAPI()">Login</button>
<div name="puthere" id="puthere"></div>
<button class="btn btn-primary" type="button" onclick="postNewRecord()">Post New Record</button>
<div name="puthere2" id="puthere2"></div>
<script>
$(document).ready(function () {
});
var token = '';
var loginAPI = function () {
var myData =
{
Name: 'admin',
Password: 'secret'
};
var myDataString = JSON.stringify(myData);
$.ajax({
type: 'POST',
url: 'http://localhost:3700/login',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: myDataString,
success: function (results) {
$('#puthere').empty();
var html = '';
$.each(results, function (index, value) {
html = html + '<p>' + index + ' : ' + value + '</p>';
if (index == 'token') {
token = value;
};
});
$('#puthere').append(html);
},
error: function (xhr, textStatus, error) {
alert(Error);
alert(xhr.statusText);
alert(textStatus);
alert(error);
}
});
};
var postNewRecord = function () {
var myData =
{
Id: '0',
Name: 'Soccer Ball',
Category: 'Sports',
Description: 'Round ball for playing the beautiful game',
Price: '13.75'
};
var myDataString = JSON.stringify(myData);
$.ajax({
type: 'POST',
url: 'http://localhost:3700/products',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: myDataString,
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', token)
},
success: function() {
alert('Saved successfully!');
},
error: function () {
alert('Something went very wrong!');
}
});
};
</script>

EDIT: In your Startup-Class you have defined a different key than the one you are using when you generate your token. They also have to be the same. I'd suggest you to read a little about JWT and how it works here.
So first thing I noticed is that you removed the issuer from the token you generate in the back-end. You have to define them because you also set them up in your Startup-Class.
Secondly your ajax request header must be set like this:
xhr.setRequestHeader('Authorization', 'Bearer ' + token)
You have to define the authorization-type within the headers-value.

Related

JSON object to ASP.NET MVC

I know there are multiple threads around this issue, but I still can't figure mine out. Can someone please help me figure out why my classObject always has null value? I feel like I've tried everything by now.
My class:
public class ClassAB
{
[Required]
[MaxLength(100)]
[DataType(DataType.Text)]
public string A{ get; set; }
[Required]
[MaxLength(100)]
[DataType(DataType.MultilineText)]
public string B{ get; set; }
}
My home controller:
[HttpPost]
public ActionResult MyMethod(ClassAB classObject)
{}
and my Javacript call
let data = {
"A": "A",
"B": "B"
}
await fetch(`https://localhost:44359/Home/MyMethod/`, {
method: "POST",
body: JSON.stringify(data),
contentType:"application/json",
success: (result)=>{
console.log(result)
},
failure: (result) => {
alert(result)
}
});
Found the issue. My contentType should have been in header. Modifying request to
await fetch(`https://localhost:44359/Home/MyMethod/`, {
method: "POST",
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
success: (result)=>{
console.log(result)
},
failure: (result) => {
alert(result)
}
});
fixed the issue
Try this
var data = [{A: 'A',B:'B'}];
await fetch(`https://localhost:44359/Home/MyMethod/`, {
method: "POST",
body: JSON.stringify(data),
contentType:"application/json",
success: (result)=>{
console.log(result)
},
failure: (result) => {
alert(result)
}
});
[HttpPost]
public ActionResult MyMethod(List<ClassAB > classObject)
{}
WebAPI won't know to model bind an object like that. See https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1
Try using the [FromBody] attribute
[HttpPost]
public ActionResult MyMethod([FromBody] ClassAB classObject)
{}
When combining this with a proper javascript post this will work, see image.
Sample js
<script>
var xhr = new XMLHttpRequest();
var url = "https://localhost:5001/api/default/MyMethod";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
console.log(json.email + ", " + json.password);
}
};
var data = JSON.stringify({ "A": "A", "B": "B" });
xhr.send(data);
</script>

Why my post dall does not get data in MVC and Angular 7

i have below code i want to post data from angular to Asp.net MVC but in object its null. method was call but parameter was null any idea why ??
ANGULAR
var result = { SearchText: "PARK"};
this.httpClient.post(
'http://localhost:55063/Common/PostAddress',result
).subscribe((res: any[]) => {
console.log(res);
this.data = res;
});
MVC
public class CommonController : Controller
{
protected SCommon sCommon = null;
public async Task<ActionResult> PostAddress(RoleModel Id)
{
sCommon = new SCommon();
var User = await sCommon.GetAddress(Id.SearchText).ConfigureAwait(false);
return Json(User, JsonRequestBehavior.AllowGet);
}
}
public class RoleModel
{
public string SearchText { get; set; }
}
try adding header to your post and stringify your body
const httpOptions = {
headers: new HttpHeaders({
"Content-Type": "application/json"
})
};
var result = { SearchText: "PARK"};
const body = JSON.stringify(result);
this.httpClient.post('http://localhost:55063/Common/PostAddress',body,httpOptions).subscribe((res: any[]) => {
console.log(res);
this.data = res;
});
aslo enable cors like this in your Register method
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);

401 Unauthorized : WWW-Authenticate: Bearer

I've seen similar threads to this issue, but I had no luck solving it.
LogIn worked successfuly but when I try to GET data of the user loggedIn in my home page I receive this error 401 Unauthorized the same erroe is showen also in Postman
My startup.cs
public class Startup
{
private string _connectionString=null;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Inject AppSettings
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
_connectionString = Configuration["secretConnectionstring"];
//without this it will define for example id to Id
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(options => {
var resolver = options.SerializerSettings.ContractResolver;
if (resolver != null)
(resolver as DefaultContractResolver).NamingStrategy = null;
});
services.AddEntityFrameworkNpgsql()
.AddDbContext<ApiContext>(
opt => opt.UseNpgsql(_connectionString));
services.AddEntityFrameworkNpgsql()
.AddDbContext<AuthentificationContext>(
options => options.UseNpgsql(_connectionString));
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<AuthentificationContext>();
services.Configure<IdentityOptions>(options => {
options.Password.RequireDigit = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 4;
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
// Jwt Authentification
var key = Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:JWT_Secret"].ToString());
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x=> {
x.RequireHttpsMetadata = false;
x.SaveToken = false;
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});
services.AddTransient<dataSeed>();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, dataSeed seed)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
// global policy - assign here or on each controller
app.UseCors("CorsPolicy");
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
app.UseAuthentication();
}
}
}
UserprofileController
{
[Route("api/[controller]")]
[ApiController]
public class UserProfileController : ControllerBase
{
private UserManager<ApplicationUser> _userManager;
public UserProfileController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpGet]
[Authorize]
//GET : /api/UserProfile
public async Task<Object> GetUserProfile()
{
string userId = User.Claims.First(c => c.Type == "UserID").Value;
var user = await _userManager.FindByIdAsync(userId);
return new
{
user.fullName,
user.Email,
user.UserName
};
}
}
}
UserServices
headers = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
}
readonly BaseUrl = 'http://localhost:53847/api';
constructor(private fb: FormBuilder, private http: HttpClient) { }
formModel = this.fb.group({
UserName: ['', Validators.required],
Email: ['', Validators.email],
fullName: ['', Validators.required],
Passwords: this.fb.group({
Password: ['',[Validators.required, Validators.minLength(4)]],
ConfirmPassword: ['', Validators.required],
}, { validator : this.comparePasswords})
});
comparePasswords(fb: FormGroup) {
let confirmPswdCtrl = fb.get('ConfirmPassword');
//passowrdMismatch
//confirmPswdCtrl.errors={passowrdMismatch:true}
if (confirmPswdCtrl.errors == null || 'passowrdMismatch' in confirmPswdCtrl.errors) {
if (fb.get('Password').value != confirmPswdCtrl.value)
confirmPswdCtrl.setErrors({ passowrdMismatch: true });
else
confirmPswdCtrl.setErrors(null);
}
}
register() {
var body = {
UserName: this.formModel.value.UserName,
Email: this.formModel.value.Email,
fullName: this.formModel.value.fullName,
Password: this.formModel.value.Passwords.Password,
};
return this.http.post(this.BaseUrl + '/ApplicationUser/Register', body, this.headers);
}
login(formData) {
return this.http.post(this.BaseUrl + '/ApplicationUser/Login', formData, this.headers);
}
getUserProfile() {
var tokenHeader = new HttpHeaders({ 'Authorization': 'Bearer' + localStorage.getItem('token'), 'Content-Type': 'application/json' });
return this.http.get(this.BaseUrl + '/UserProfile', { headers: tokenHeader });
}
}
ApplicationUserController the PostMethod
[HttpPost]
[Route("Login")]
//POST : /api/ApplicationUser/Login
public async Task<IActionResult> Login(LoginModel model)
{
var user = await _userManager.FindByNameAsync(model.UserName);
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserID",user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.JWT_Secret)), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
else
return BadRequest(new { message = "Username or password is incorrect." });
}
}
Help Plz .. Thx
In my case I wasn't getting an error and everything appeared to work but my API returned 401 every time.
Having banged my head a lot on this.
I had ...
[Authorize]
on my Controller and found that the site was trying to use cookie authentication so although my JWT worked fine, the lack of a cookie auth made it fail.
I changed the attribute to ...
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
and this fixed the issue as now the controller ignores cookie auth and concentrates only on jwt.
Hope this helps someone
I found that changing the order of statements was my problem. Configure() requires ASP.NET Core middleware to be in the correct order.
This DID NOT work, and required me to add [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] to every controller...
app.UseAuthorization();
app.UseAuthentication();
This DOES work:
app.UseAuthentication();
app.UseAuthorization();
One problem I see is here:
var tokenHeader = new HttpHeaders({ 'Authorization': 'Bearer' + localStorage.getItem('token'), 'Content-Type': 'application/json' });
When specifying a Bearer token, you need to leave a space between Bearer and the token itself, so that the result looks like this:
Authorization: <type> <credentials>
In your case, that would translate to:
Authorization: Bearer token
However, if you look at the code above, you'll see you're actually going to supply it like so:
Authorization: Bearertoken
which isn't going to work. Therefore, change your code to be:
var tokenHeader = new HttpHeaders({ 'Authorization': 'Bearer ' + localStorage.getItem('token'), 'Content-Type': 'application/json' });
// ---------------------------------------------------------^ Notice I've added a space here.
The code you show does not have the UseAuthorization() declaration.
In .NET 6 I am doing:
builder.Services.AddAuthentication(opt =>
{
opt.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:XXXX";
options.Audience = "idwebclient";
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("XXXXXXXXXXXXXXXXXXXXXXXXX"));
});
Recap
In my case I was not using any Identity Server Yet I was providing the Host as a ValidIssuer.
It validated the Authority for the algo and keys which returned nothing, this caused the system to throw an unhandled exception.
Solved this By Removing options.Authority from JwtBearerOptions in AddJwtBearer(options => ...).
After that I faced the 401 ERROR, resolved it by removing options.Audience from JwtBearerOptions in AddJwtBearer(options => ...), Also added ValidateLifetime to TokenValidationParameters (which you can see below in part 1)
Code
PART (1) JWT Configuration
in .NET 6 :
builder.services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = jwtSettings.ValidateIssuerSigningKey,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.IssuerSigningKey)),
ValidateIssuer = jwtSettings.ValidateIssuer,
ValidIssuer = jwtSettings.ValidIssuer,
ValidateAudience = jwtSettings.ValidateAudience,
ValidAudience = jwtSettings.ValidAudience,
RequireExpirationTime = jwtSettings.RequireExpirationTime,
ValidateLifetime = jwtSettings.RequireExpirationTime,
ClockSkew = TimeSpan.FromDays(1),
};
});
Extra
GET your JWT Settings from Appsettings using Either this
Where
"JsonWebTokenKeys"
is the name of section in configuration :
var jwtSettings = new JwtSettings();
Configuration.Bind("JsonWebTokenKeys", jwtSettings);
builder.services.AddSingleton(jwtSettings);
//PART (1) => JWT Configuration goes here
//..
//..
OR this :
services.Configure<JwtSettings>(configuration.GetSection("JsonWebTokenKeys"));
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
var jwtSettings = serviceProvider.GetRequiredService<IOptions<JwtSettings>>().Value;
//PART (1) => JWT Configuration goes here
//..
//..
}

InvalidOperationException: Incorrect Content-Type: Microsoft.AspNetCore.Http.Features.FormFeature.ReadForm()

I am new to Asp.net MVC Core. I am working on Server-side loading of JQuery Datatables.net using Asp.Net Core MVC Middleware.
I have used this tutorial to learn how to create a handler and then this article to migrate to middleware but are running into some issues that I hope you can help me with.
I have refined using this tutorial
I get error
"InvalidOperationException: Incorrect Content-Type: Microsoft.AspNetCore.Http.Features.FormFeature.ReadForm()"
when I run the solution.
Here is my code:
View
<script type="text/javascript">
$(document).ready(function () {
$('#datatable').DataTable({
//"paging": true,
//"ordering": true,
//"info": true,
'columns' : [
{ 'data': 'InsertedDateUtc' },
//{ 'data': 'EventId' },
{ 'data': 'UserId' },
{ 'data': 'Action' },
{ 'data': 'Context' },
{ 'data': 'RecordId' },
{ 'data': 'Property' },
{ 'data': 'OldValue' },
{ 'data': 'NewValue' },
],
'processing': true,
'serverSide': true,
'ajax' : {
'type' : 'POST',
'url' : '../AuditEventData.cs',
//'url': '../APIController/GetAuditEvents'
//'url' : '#Url.Action("GetAuditEvents", "APIController")'
'datatype': 'json',
}
});
});
</script>
Middleware
public class AuditEventData
{
private readonly RequestDelegate _next;
private readonly IDataGet _dataGet;
public AuditEventData(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
string result = null;
int filteredCount = 0;
var draw = httpContext.Request.Form["draw"].FirstOrDefault();
var start = int.Parse(httpContext.Request.Form["start"].FirstOrDefault());
var length = int.Parse(httpContext.Request.Form["length"].FirstOrDefault());
var sortCol = int.Parse(httpContext.Request.Form["columns[" + httpContext.Request.Form["order[0][column]"].FirstOrDefault() + "][name]"].FirstOrDefault());
var sortDir = httpContext.Request.Form["order[0][dir]"].FirstOrDefault();
var search = httpContext.Request.Form["search[value]"].FirstOrDefault();
try
{
var auditEvents = await _dataGet.GetServerSideAuditEvents(length, start, sortCol, sortDir, search);
filteredCount = auditEvents.Count();
var data = new
{
iTotalRecords = await _dataGet.GetTotalAuditEventCount(),
iTotalDisplayRecords = filteredCount,
aaData = auditEvents
};
result = JsonConvert.SerializeObject(data);
await httpContext.Response.WriteAsync(result);
}
catch (Exception e)
{
await ErrorHandler.HandleException(e);
}
await _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseAuditEventDataMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<AuditEventData>();
}
}
Startup.cs
app.MapWhen(
context => context.Request.Path.ToString().EndsWith("ViewAudit"),
appBranch =>
{
appBranch.UseAuditEventDataMiddleware();
});
In the middleware class the line
var start = int.Parse(httpContext.Request.Form["start"].FirstOrDefault());
gives me the error - the tutorials and Microsoft documentation here seem to indicate that I do not need to use the ".Form" and should be able to just use
var start = int.Parse(httpContext.Request["start"].FirstOrDefault());
however, when I do that, I get this error
cannot apply indexing with [] to an expression of type 'HttpRequest'
I cannot find any examples on how to do this and any help will be appreciated
Thanks
In order to expect to have a Form in your HttpContext.Request you must change your ajax datatype to 'application/x-www-form-urlencoded'. Now whether you want to do that is another question.
From here: https://developer.mozilla.org/en-US/docs/Learn/Forms/Sending_and_retrieving_form_data

Can't invoke client functions from the Server

I'm working on a small concept trying to get to work with SignalR.
In this scenario all connected clients need to refresh when one of the clients perform an update, this update is performed through an Ajax request.
Because there might be different instances of the page, depending on a parameter, I'm using groups for this.
The client side code looks as followed:
<script>
window.onload = function () {
DoRefresh();
}
$(function () {
var hub = $.connection.commonHub;
hub.client.refresh = function () {
DoRefresh();//This doesn't get called for the other clients
};
$.connection.hub.start();
$.connection.hub.start().done(function () {
hub.server.join("MyPage" + #Model.Id + "");
});
});
function htmlEncode(value) {
var encodedValue = $('<div />').text(value).html();
return encodedValue;
}
function DoRefresh() {
$.ajax({
url: '#Url.Action("LoadData")',
cache: false,
data: "&id=" + #Model.Id + "",
success: function (html) {
$("#conversation").empty();
$("#conversation").append(html);
},
error: function (xhr, status, err) {
alert('Response code:' + xhr.status + '\r\n[Error:' + err + '] ' + status);
}
});
return false;
};
function DoUpdate() {
$.ajax({
url: '#Url.Action("DoUpdate")',
cache: false,
data: "&id=" + #Model.Id + "&posterID=" + #userAccount.AccountID + "&message=" + $("#Textbox").val().replaceAll("\n", "[br]"),
success: function () {
$("#Textbox").empty();
DoRefresh();
},
error: function () {
$("#Textbox").empty();
DoRefresh();
}
});
return false;
};
</script>
In my controller, following functions are part of this scenario:
public class MyController : Controller
{
private Hubs.CommonHub hub = new Hubs.CommonHub();
//Some other Controller Methods
public PartialViewResult LoadData(int id)
{
MyModel item = Connection.DB.MyData.FirstOrDefault(x => x.Id == id);
return PartialView(item);
}
public virtual EmptyResult DoUpdate(int id, int posterID, string message)
{
message = message.Replace("[br]", "\r\n");
MyModel item = Connection.DB.MyData.FirstOrDefault(x => x.Id == id);
Account poster = Connection.DB.Accounts.FirstOrDefault(x => x.Id == posterID);;
item.Updates.Add(new Update()
{
PosterId = posterID,
Poster = poster,
Id = id,
Item = item,
PostDate = DateTime.UtcNow,
Message = message.Replace(Environment.NewLine,"\r\n")
});
Connection.DB.SaveChanges();
hub.Refresh("MyPage" + item.Id);
return null;
}
}
And finally, my hub class looks like this:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.SignalR;
using MyProject.Models;
namespace MyProject.Hubs
{
public class CommonHub : Hub
{
private string myInfo;
public override Task OnConnected()
{
myInfo = Context.ConnectionId;
return base.OnConnected();
}
public Task Join(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task Leave(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public void Refresh(string groupName)
{
var context = GlobalHost.ConnectionManager.GetHubContext<CommonHub>();
context.Clients.Group(groupName).Refresh();
}
}
}
The Join task in the hub is called with every connect of a new browser window. The Refresh method is invoked on the hub, but only the invoking window has its page refreshed. The client side refresh function only gets invoked once when debugging, probably for the invoking client.
I have no idea why the other clients are not doing their updates, please help.

Resources