in asp.net core I made a service to add new row and another one to update row. I call the service from the controller based on JS fetch request. now I need from the service response to return partialView and some Json like that
var data = { htmlContent : '<html table row >', mode : 'insert', isSuccessful : true }
// then when I get response from the controller
.then((data) => {
jsonObj.parse(date);
// ... some logic as follows
if (jsonObj.mode == 'create')
{
if (isSuccessful === 'true') {
// ... appendChild with [jsonObj.htmlContent]
} else {
// ... show modal with [jsonObj.htmlContent]
}
} else if (jsonObj.mode == 'update') {
if (isSuccessful === 'true') {
// ... insertInto (index) with [jsonObj.htmlContent]
} else {
// ... show modal with [jsonObj.htmlContent]
}
}
} else if (jsonObj.mode == 'delete') {
if (isSuccessful === 'true') {
// ... remove element with temp message [jsonObj.htmlContent]
} else {
// ... show modal with [jsonObj.htmlContent]
}
}
});
for the service I return response class like this
// service
public addNew()
{
try(){
_dbContext.add(...
var result = _dbContext.saveChanges();
// here is the question
// this is service not ActionResult
// I need to return here html content out of partialView
// along with some other Json data
return new response{htmlContent: partialView(''), mode: 'insert', isSuccessful: true};
}catch
{
return new response{htmlContent: '<h1>something went wrong !</h1>', mode: 'insert', isSuccessful: false};
}
}
public class Response
{
public Dynamic htmlContent { get; set; }
public bool isSuccessful { get; set; }
public Crud mode { get; set; }
}
public enum Crud () {
create, review, update, delete
}
in the controller I return to js the addNew result which is Response class
// after injection I use the service here
public IActionResult manage()
{
return _myService.addNew();
}
finally and again is possible to return partialView along with some Json data from the microservice like shown above
return new response{htmlContent: partialView(''), mode: 'insert', isSuccessful: true};
I think returning partialView is not allowed without IActionResult, however geeks absolutely know someway
A PartialView can only be interpreted and resolved to HTML by a razor view.
In your case, you are calling an API directly from Javascript so you have no razor component to interpret a PartialView response.
You either need to roll with the framework and use razor view or use javascript (or jQuery) to create the html that you want.
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);
}
};
});
Simple usecase - get PartialView dynamically from AJAX call to update div in my main page after input select (dropdownlist) changed value.
Steps I took:
Created view (only, wihtout PageModel) with model declared with #model ViewModelCreateOperation.
Created checkbox on main page:
<select class="form-control" asp-items="#(new SelectList(Model.allExistingOperations))" onchange="PopulateForm(this.value); return false;"></select>
created scripts on main page:
<script>
function PopulateForm(value) {
var dataToPost = "{ operationName:" + value + "}";;
$.ajax({
type: "post",
url: '#Url.Content("/MeaningOfLifeRoutedName")',
data: dataToPost ,
contentType : 'application/json; charset=UTF-8',
success: function (data) {
$('#lubieplacki').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
if (xhr.status == 404) {
alert(thrownError);
}
}
});
}
</script>
created Controller in Controllers folder to return PartialView (becouse I cannot use "return PartialView("someview", someModel)" with PageModel already used as a inherit class.
namespace MyMysteriousApplication.Controllers
{
[Route("MeaningOfLifeRoutedName")]
public class MeaningOfLifeChangesController : Controller
{
private readonly MyMysteriousApplication.Models.TTSCDBContext _context;
public MeaningOfLifeChangesController(MyMysteriousApplication.Models.TTSCDBContext context)
{
_context = context;
}
public ViewModelCreateOperation viewModelCreateOperation { get; set; }
public IActionResult Index()
{
return RedirectToPage("../Index");
}
[HttpPost]
public ActionResult getMeaningOfLife(string operationName)
{
viewModelCreateOperation = new ViewModelCreateOperation();
viewModelCreateOperation = new ViewModelCreateOperation();
viewModelCreateOperation._entitiesSelectListItem = _context.Entities
.Select(a => new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem()
{
Value = a.Id.ToString(),
Text = a.EntityName
}).OrderByDescending(u => u.Text)
.ToList();
viewModelCreateOperation.MeaningOfLifeChanges = _context.MeaningOfLifeChanges.Where(u => u.OperationName.Contains(operationName)).OrderBy(u => u.ChangeId).FirstOrDefault();
return PartialView("../projectManagement/partialViewCreateNewMOL", viewModelCreateOperation);
}
}
}
Primary question:
I got null in parameters - I don't get why:
Bonus question:
I couldn't invoke my controller in any way (tried "/MeaningOfLifeChangeController/getMeaningOfLife" or "/MeaningOfLifeChange/getMeaningOfLife", with "~/MeaningOfLifeChangeController/getMeaningOfLife" and others combinations), so I added [Route("MeaningOfLifeRoutedName")] and [HttpPost] before method. I don't get why...
in Startup I have added controllers to initialize (JSON is for other stuff(API)):
services.AddControllersWithViews().
AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.MaxDepth = 150;
}).AddRazorRuntimeCompilation();
It's not my answer, but Jiadong Meng helped me in ASP .NET Forums. I'm posting His answer:
Since the data you want to send is just a string type data, you need to stringify it like below.
var dataToPost = JSON.stringify(value);
Then in your Action, you should also add [FromBody] attribute.
public ActionResult getMeaningOfLife([FromBody]string operationName)
I am having a modal popup to create event and i want to allow only login user to see that page, save event and fetch event all are using json, but when i am going back after logout, session value is still present and all actions are getting performed, until i donot refresh the page, i want that no action should happen and session value should be cleared when i logout
public ActionResult Index()
{
if(Session["UserID"]==null)
{
return RedirectToAction("Index2","Login");
}
else
{
TempData["usersession"] = Session["UserID"].ToString();
}
return View();
}
<label id="session">#TempData["usersession"]</label>
//Javascript and Json
$(document).ready(function () {
username = $('#session').text();
});
function SaveEvent(data) {
alert(username);
$.ajax({
type: "POST",
url: '/home/SaveEvent',
data: data,
success: function (data) {
if (data.status) {
//Refresh the calendar
fetchEvent();
$('#myModalSave').modal('hide');
//alert(username);
}
},
error: function () {
alert('failed');
}
});
While i am trying to alert username when i click on save it still showing the session value
have you tried this?
Session.Abandon(); // The Abandon method destroys all the objects stored in a Session object and releases their resources.
Session.Remove("YourItem"); //just removes current values
Session.Clear();// just removes all values
https://stackoverflow.com/a/5330288/7262120
public class VerifyUserAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var user = filterContext.HttpContext.Session["UserID"];
if (user == null)
filterContext.Result = new RedirectResult(string.Format("/User/Login?targetUrl={0}",filterContext.HttpContext.Request.Url.AbsolutePath));
}
}
[VerifyUserAttribute]
public ActionResult Index()
{
if(Session["UserID"]==null)
{
return RedirectToAction("Index2","Login");
}
else
{
TempData["usersession"] = Session["UserID"].ToString();
}
return View();
}
I have a simple function that searches for item I want in my database and retrieves it in my controller.
[HttpPost]
public ActionResult Index(string searchString)
{
var user = from m in db.Users select m;
if (!String.IsNullOrEmpty(searchString))
{
user = user.Where(s => s.UserName.Contains(searchString));
}
return View(user);
}
And then in my Javascript I send a value to search:
$('#test').click(function(e) {
e.preventDefault();
var user = "John";
$.ajax({
url: "#Url.Action("Index", "Users")",
data { "searchString": user },
type: "post",
success: function (saveResult) {
console.log(saveResult);
},
error: function(xhr, ajaxOptions, thrownError) {
console.log(xhr, ajaxOptions, thrownError);
}
})
})
However of course all this does it return my view inside the console window which is something like:
But I would like to return a json object I can use.
just use the Json Action method.
return Json(user);
Edit:
As a side note, I would also set my return Type to be JsonResult for clarity
You just return as JsonResult such as below:
public ActionResult SomeActionMethod() {
return Json(new {foo="bar", baz="Blech"});
}
Razor:
#Html.TextBoxFor(kod => kod.Name)
#Html.ValidationMessage("Name","Client Error Message")
Controller:
[HttpPost]
public JsonResult JsonAddCustomer(Customers customer, string returnUrl)
{
if (customer.Name.Trim().Length == 0)
{
ModelState.AddModelError("Name", "Server Error Message");
}
//Eğer hata yoksa veri tabanına kayıt yapılıyor.
if (ModelState.IsValid)
{
try
{
CusOpp.InsertCustomer(customer);
return Json(new { success = true, redirect = returnUrl });
}
catch (Exception e)
{
ModelState.AddModelError("", "Error");
}
}
return Json(new { errors = GetErrorsFromModelState() });
}
I want to write validation error message. I did this like above, but #Html.ValidationMessage("Name","Client Error Message") does not work. In fact, I was already expecting it.
I want to show like this statement's result: #Html.ValidationMessageFor(m => m.name) ,but I cant use this, because I used entity-data-model.
Should I add [Required] statement to data-model classes or any way that I do this. Sorry for bad explanation.
Thanks.
You should return PartialViews instead of JSON in this case. Only in the case of success you could return JSON:
[HttpPost]
public ActionResult JsonAddCustomer(Customers customer, string returnUrl)
{
// Warning: the following line is something horrible =>
// please decorate your view model with data annotations or use
// FluentValidation.NET to validate it.
// Never write such code in a controller action.
if (customer.Name.Trim().Length == 0)
{
ModelState.AddModelError("Name", "Server Error Message");
}
//Eğer hata yoksa veri tabanına kayıt yapılıyor.
if (ModelState.IsValid)
{
try
{
CusOpp.InsertCustomer(customer);
return Json(new { success = true, redirect = returnUrl });
}
catch (Exception e)
{
ModelState.AddModelError("", "Error");
}
}
return PartialView(customer);
}
Now inside the success callback of your AJAX request you can test whether the POST action succeeded or not:
success: function(result) {
if (result.redirect) {
// we are in the success case => redirect
window.location.href = result.redirect;
} else {
// a partial view with the errors was returned => we must refresh the DOM
$('#some_container').html(result);
// TODO: if you are using unobtrusive client side validation here's
// the place to call the $.validator.unobtrusive.parse("form"); method in order
// to register the unobtrusive validators on the newly added contents
}
}
Here's a similar post that you might also read through.
Your Idea with the Required annotation on the model is a good approach. You can set a Error Message on the Required annotation.
[Required(ErrorMessage = "Please enter a name")]
and remove your if in your action..this:
if (customer.Name.Trim().Length == 0)
{
ModelState.AddModelError("Name", "Server Error Message");
}
the ModelState.IsValid will do the job for you on the client and server side.
And use your #Html.ValidationMessageFor(m => m.name) in your view