I have the following code in a Blazor app that calls an API and retrieved a list of Items based on the user id parameter.
The Http.GetJsonAsync does not seem to allow me to enter a parameter. How do I specify the userid variable as the parameter?
Calling the API
protected override async Task OnInitializedAsync()
{
userid = await localStorage.GetItemAsync<string>("userid");
myTrades = await Http.GetJsonAsync<ItemForTrade[]>("api/Trading/GetTradesForUser");
}
The API code
[Authorize]
[HttpGet("[action]")]
public async Task<List<ItemForTrade>> GetTradesForUser(string userid)
{
return await tradingService.GetTradesForUser(userid);
}
HttpGet passes parameters through the URL,so it's as easy as adding ?userid=value to the URL like this:
myTrades = await Http.GetJsonAsync<ItemForTrade[]>("api/Trading/GetTradesForUser?userid=" + userid);
Related
I'm developing ASP Core Web API using dotnet core v3.1.
I'm using JWT tokens for authentication. And for authorization I use the [Authorize] attribute.
How can I create my own response if the user is not logged in (while trying to access the action marked with the [Authorize] attribute) or the user's token is not authenticated.
I came across a solution using a custom authorization attribute inherited from the default one. And in this example, the HandleUnauthorizedRequest method is overridden. But I don't see such a method inside the AuthorizeAttribute class.
Is there a way to create custom unauthorized responses with http body?
Since you are using JWT bearer authentication, one way to override the default Challenge logic (which executes to handle 401 Unauthorized concerns) is to hook a handler to the JwtBearerEvents.OnChallenge callback in Startup.ConfigureServices:
services.AddAuthentication().AddJwtBearer(options =>
{
// Other configs...
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
// Call this to skip the default logic and avoid using the default response
context.HandleResponse();
// Write to the response in any way you wish
context.Response.StatusCode = 401;
context.Response.Headers.Append("my-custom-header", "custom-value");
await context.Response.WriteAsync("You are not authorized! (or some other custom message)");
}
};
});
This will override the default challenge logic in JwtBearerHandler.HandleChallengeAsync, which you can find here for reference purposes.
The default logic does not write any content to response (it only sets the status code and set some headers). So to keep using the default logic and add content on top of it, you can use something like this:
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
context.Response.OnStarting(async () =>
{
// Write to the response in any way you wish
await context.Response.WriteAsync("You are not authorized! (or some other custom message)");
});
return Task.CompletedTask;
}
};
For .net core 5 web api project with jwt authentication use this middleware in Configure method of Startup.cs for show ErrorDto in Swagger:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LoginService v1"));
}
app.ConfigureExceptionHandler();
app.UseHttpsRedirection();
app.UseRouting();
// Unauthorized (401) MiddleWare
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized) // 401
{
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(new ErrorDto()
{
StatusCode = 401,
Message = "Token is not valid"
}.ToString());
}
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
ErrorDto :
public class ErrorDto
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
This is what I came up with for responding with the same ProblemDetails you would get from returning Unauthorized() in an ApiController:
.AddJwtBearer(options =>
{
// Other configs...
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
// Call this to skip the default logic and avoid using the default response
context.HandleResponse();
var httpContext = context.HttpContext;
var statusCode = StatusCodes.Status401Unauthorized;
var routeData = httpContext.GetRouteData();
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
var factory = httpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = factory.CreateProblemDetails(httpContext, statusCode);
var result = new ObjectResult(problemDetails) { StatusCode = statusCode };
await result.ExecuteResultAsync(actionContext);
}
};
});
I am wrote API method, after calling that method , I got my response like
[
{
"spark_version": "7.6.x-scala2.12"
}
]
Now I want to have variable in my API method which store value 7.6.x-scala2.12.
API Controller method
[HttpGet]
public IActionResult GetTest(int ActivityId)
{
string StoredJson = "exec sp_GetJobJSONTest " +
"#ActivityId = " + ActivityId ;
var result = _context.Test.FromSqlRaw(StoredJson);
return Ok(result);
}
So how this variable should call on this response to get string stored in spark_version?
Thank you
As you have the JavaScript tag, here's how you'd do it in JS:
If you are able to call your API method, then you can just assign the response to a variable. For example, if you are calling it using the fetch API, it would look something like:
let apiResponse;
fetch('myApiUrl.com').then((response) => {
if (response.status === 200) {
apiResponse = response.body;
console.log('Response:', apiResponse[0]['spark_version']);
}
});
(I defined the variable outside the then to make it globally accessible)
Let's say I post a workitem to Revit engine using the following code
var response = await _flurlClient.Request("https://developer.api.autodesk.com/da/us-east/v3/workitems")
.PostJsonAsync(new
{
activityId = "ActivityID",
arguments = new
{
rvtFile = new
{
url = storageUrl,
Headers = new
{
Authorization = $"Bearer {accessToken}"
}
},
result = new
{
verb = "post",
url = $"{baseUrl}/api/result"
}
}
})
.ReceiveJson();
The response will contain the Id for this workitem. once the work item completes successfully, Forge calls my API endpoint with the output file. My endpoint is implemented as follows:
[HttpPost("Result")]
public async Task<IActionResult> PostResults()
{
await using (var fs = new FileStream("D://Test//l2.xlsx", FileMode.Create))
{
await Request.Body.CopyToAsync(fs);
}
return Ok();
}
The file is correctly saved but I can't get the associated workitem Id (not as a query parameter nor a header). This causes an issue, let's say I submitted two work items (A and B) when I receive a file how I can tell if it is related to work item A or B.
See answer.
Design automation allows you to specify a variable workitem.id in your output url. When your workitem completes we shall call this url with the variable expanded to the id of that workitem. This dynamic variable path allows you to determine the workitem id associated with that callback.
At your end you will need to implement Attribute Routing.
[HttpPost("Result/{workitemId}")]
public async Task<IActionResult> PostResults(string workitemId)
{
}
You may post the workitem with:
result = new
{
verb = "post",
url = $"{baseUrl}/api/result/$(workitem.id)"
}
My drop down where I am getting my groupId
<select id="GroupDropdown" name="GroupDropdown" onchange="Filteration();" class="form-control"></select>
MY Javascript ajax
function Filteration()
{
var groupid = document.getElementById("GroupDropdown").value;
var subgroupid = document.getElementById("SubGroupDropdown").value;
var locationid = document.getElementById("LocationID").value;
var propertyname = document.getElementById("txtPropertyName").value;
var propertydescription = document.getElementById("Description").value;
$.ajax({
type: 'GET',
contentType: "application/json;charset=utf-8",
url: "/Home/Index",
data: { 'GroupId': groupid, 'SubGroupId': subgroupid, 'LocationId': locationid, 'PropertyName': propertyname, 'Description': propertydescription},
dataType: "json",
context: this,
success: function (data) {
console.log(data);
},
error: function (request) {
}
});
}
Hi in my controller I had written a code which every time I get new data according to GroupId and other data pass.
public ActionResult Index(string GroupId, string SubGroupId, string LocationId, string PropertyName, string Description)
{
using (SqlConnection con = new SqlConnection(constr))
{
using (SqlCommand cmd = new SqlCommand("InsUpsDelTbl_Property"))
{
try
{
DataSet GetData = new DataSet();
cmd.Connection = con;
con.Open();
cmd.Parameters.AddWithValue("#Operation", "GetPropertyDataFilteration");
cmd.Parameters.AddWithValue("#GroupID", GroupId);
cmd.Parameters.AddWithValue("#SubGroupID", SubGroupId);
cmd.Parameters.AddWithValue("#LocationID", LocationId);
cmd.Parameters.AddWithValue("#Title", PropertyName);
cmd.Parameters.AddWithValue("#Description", Description);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter ad = new SqlDataAdapter(cmd);
ad.Fill(GetData);
ViewBag.tblProperty = GetData.Tables[1];
con.Close();
}
catch (Exception ex)
{ throw ex; }
}
}
return View();
}
Here in above code on basis of groupid, subgroupid the data of ViewBag.tblProperty get changed but the data displaying on view is not changing. The view is displaying same data retrieved initially from viewbag.tblProperty when page get loads for the 1st time.
My view page code
#foreach (System.Data.DataRow dr in ViewBag.tblProperty.Rows)
{
<p><b>Group:</b> #dr["GroupName"].ToString()</p>
<p><b>SubGroup:</b> #dr["SubGroupName"].ToString()</p>
<p><b>Location:</b> #dr["LocationName"].ToString()</p>
<p><b>Age:</b> #dr["Age"].ToString() Years</p>
}
initially on page load event the group id, subgroup id is used to pass as empty so at that time 4 data used to get retrieved from database and used to get that data bind in viewbag.tblProperty but after changing of group id, subgroup id data only 2 data gets retrieved from database and viewbag.tblProperty again get bind with that data but on view page same old record with 4 data is used to display.
How can I change the view on basis of data retrieved from database
When you change your dropdown, you are calling the Filteration javascript method which does an ajax call. In your action method, you are setting the viewbag and returning the view. But your original razor view code was already executed and the output of which you are currently seeing in your browser).
You have 2 options.
1. Do not use ajax call. Reload the GET action
So instead of making the ajax call, you make a normal HTTP request to the GET action method with the selected values as querystring params. With your current action method code, it sets the viewbag data and return the view. When razor executes the code it will render the new data in the viewBag.
function Filteration()
{
var groupid = $("#GroupDropdown").val();
var subgroupid = $("#SubGroupDropdown").val();
var locationid = $("#LocationID").val();
var propertyname = $("#txtPropertyName").val();
var propertydescription = $("#Description").val();
var baseUrl="/Home/Index?groupId="+groupId+"&subgroupid="+subgroupid+
"&locationId="+locationid+"&propertyname="+propertyname+
"&propertydescription"=propertydescription ;
window.location.href=baseUrl;
}
2. Use ajax, but have the action method return partial view result.
Have your action method return a partial view result for ajax call. So create a partial view and return that when the request is an ajax call and update the DOM of current page when you receive the successful response from the ajax call.
So create a partial view called ResultList and it will have this code
#foreach (System.Data.DataRow dr in ViewBag.tblProperty.Rows)
{
<p><b>Group:</b> #dr["GroupName"].ToString()</p>
<p><b>SubGroup:</b> #dr["SubGroupName"].ToString()</p>
<p><b>Location:</b> #dr["LocationName"].ToString()</p>
<p><b>Age:</b> #dr["Age"].ToString() Years</p>
}
and your action method return this partial view
if(Request.IsAjaxRequest())
{
return PartialView("ResultList");
}
return View();
Now go to the main view and replace the loop code with a call to the partial and wrap it inside a container div.
<div id="results">
#Html.Partial("ResultList")
</div>
Now on your ajax call's success/done event, update the content of this container div with the results coming from the ajax call.
success: function (data) {
$("#results").html(data);
},
can someone explain me how to implement caching of JsonResult actions in MVC 5 application?
I want to use caching of some ajax-called actions using [OutputCache()] attribute. Some of these actions return ActionResult with html-content, some JsonResult with serialized lists of {Id, Title} pairs which I'm going to use to construct dropdown lists.
My goal is to reduce amount of DB-queries (while building ViewModels) and server requests (when using ajax-calls for it).
So, my code looks like snippets below:
[OutputCache(Duration=60*60*24)]
public async Task<ActionResult> SearchCaseOrgDialog(){
//extract data return html page
return View();
}
[OutputCache(Duration=60*60*24)]
public async Task<JsonResult> AjaxOrgDepartments(){
//query database, serialize data, return json
var result = await ctx.OrgDepartments
.Select(d => new {
Id = d.Id,
Title = d.Title }
)
.ToListAsync();
return Json(result, JsonRequestBehavior.AllowGet);
}
When I look at FireFox tools-panel I see next picture for Html-content:
Here Firefox uses client-side cached version of ajax-requested page.
But situation differs with json-content:
It doesn't cache content, and seems to transfer data from server (server-side cache).
In both cases response headers look the same:
Cache-Control:"public, max-age=86400, s-maxage=0"
Content is requested using similar ajax-calls like
$.get(url, null, function(data){
//do something with data
});
So, how do I cache json-content? what is the right way to do it, and why default approach does not work?
If you want to avoid DB queries, you should consider caching the data at server side. You can use MemoryCache class to do that.
Quick sample
public class MyLookupDataCache
{
const string categoryCacheKey = "CATEGORYLIST";
public List<string> GetCategories()
{
var cache = MemoryCache.Default;
var items = cache.Get(categoryCacheKey);
if (items != null)
{
CacheItemPolicy policy = new CacheItemPolicy();
policy.AbsoluteExpiration = DateTime.Now.AddDays(7); //7 days
//Now query from db
List<string> newItems = repository.GetCategories();
cache.Set(categoryCacheKey, newItems, policy);
return newItems;
}
else
{
return (List<string>) items;
}
}
}
You can change the method signature to return the type you want. For simplicity, i am using List<String>