Check if a model has data, within the view - c#

QUESTION
How do I safely and efficiently check if a view model has been populated with data, within the view itself?
EXPLANATION
I'm passing in a model to a view like so;
return View(response.Success ? (SalesDashboardViewModel)response.Model : new SalesDashboardViewModel());
Now, the view either has a fully populated view model with all the data, or, if the view model wasn't populated correctly, it may have an empty 'SalesDashboardViewModel'. If the second case is true, when I call #Model.CountOfUsers in the view for example, I will get a null object reference error.
How, in the view, would I go about checking if this is empty or not, other than checking if one of it's properties is null (due to the fact the properties may change).
FULL CODE BREAKDOWN
// Controller
public ActionResult SalesDashboard(){
var response = DashboardService.BuildSalesViewModel(User.Identity.GetUserId());
return View(response.Success ? response.Model : new SalesDashboardViewModel());
}
// Populating the SalesDashboardViewModel
public CustomResponseModel BuildSalesViewModel(string userId)
{
try
{
CustomResponseModel response;
var vm = new SalesDashboardViewModel();
response = GetCountOfSuspectsAddedThisMonth(userId);
vm.NoSuspectsAddedThisMonth = response.Success ? (int)response.Model : throw new Exception(response.Reason);
response = GetCountOfProspectsAddedThisMonth(userId);
vm.NoPropectsAddedThisMonth = response.Success ? (int)response.Model : throw new Exception(response.Reason);
response = GetCountOfCustomersNotContactedRecently(userId, 12);
vm.NoCustomersNotContactedRecently = response.Success ? (int)response.Model : throw new Exception(response.Reason);
response = GetTopProspects(userId, 10);
vm.TopProspects = response.Success ? (List<Prospect>)response.Model : throw new Exception(response.Reason);
return new CustomResponseModel { Success = true, Model = vm };
}
catch (Exception e)
{
return new CustomResponseModel
{
Success = false,
Reason = e.Message,
};
}
}
If there is a better way of going about this then I'm open to suggestions. I appreciate the help :)

You can use Safe Navigation Operator (?.) to access the properties, if you are not sure that they will have the value.
#Model?.CountOfUsers
Find more details here.

Related

Is there a way to prevent null object errors from complex objects when the page is refreshed?

I've got a controller action that is using TempData to get complex objects from another action. The issue happens when the user refreshes the page and gets null object errors on the view. The complex objects are not passed through the URL like the other values are. Is there a way to prevent this from happening? An alternative solution would be to remove all query parameters from the URL on a page refresh and display the view like it was a new object.
Controller
public IActionResult Daily(Daily daily)
{
new ReportDaily().GetAvailableSavedCriteria(out List<ReportCriteria> reportCriteria, out Notification not);
if (daily.SelectedCriteria == null) {
//Create daily report object and initialize the default values
var newModel = new Daily
{
PaymentTypes = DGetPaymentTypes(),
Users = DGetUsers(),
Criteria = reportCriteria,
StartDate = DateTime.Today.Date,
EndDate = DateTime.Today.Date,
County = true,
Municipality = true
};
return View(newModel);
}
else
{
daily.PaymentTypes = TempData.Get<List<Daily.PaymentType>>("PaymentTypes") == null ? DGetPaymentTypes() : TempData.Get<List<Daily.PaymentType>>("PaymentTypes");
daily.Users = TempData.Get<List<Daily.User>>("Users") == null ? DGetUsers() : TempData.Get<List<Daily.User>>("Users");
daily.Criteria = reportCriteria;
return View("Daily", daily);
}
}
TempData only used for a single redirect, to keep the data from another action after refreshing, you can use Session to achieve it.
To use Session in core mvc, you need to add following codes to the starup.cs fileļ¼š
Add services.AddSession(); in ConfigureServices method.
Add app.UseSession(); in Configure method.
To save complex object in Session,you can convert the list object into a json format for storage, and then deserialize it into a list object when you get it.
HttpContext.Session.SetString("PaymentTypes", JsonConvert.SerializeObject(pamentTypeList));
Daily Action:
//.....
daily.PaymentTypes = HttpContext.Session.GetString("PaymentTypes") == null ? DGetPaymentTypes() : JsonConvert.DeserializeObject<List<Daily.PaymentType>> (HttpContext.Session.GetString("PaymentTypes"));

Model Value is not setting

I am creating an CRUD Application in Asp.Net Core
After Add Operation I am redirecting to same view with setting model value as null to get another entry
Below is my code
public IActionResult Add(OptionMasterVM model)
{
try
{
model.QuestionList = context.QuestionMaster.Select(x => new SelectListItem { Text = x.QuestionName, Value = x.QuestionId.ToString() }).ToList();
if (HttpContext.Request.Method == "POST")
{
OptionMaster _optionmaster = new OptionMaster();
_optionmaster = model.OptionMaster;
using (var ctx = new QuestionnaireEntities(_configuration))
{
ctx.OptionMaster.Add(_optionmaster);
ctx.SaveChanges();
}
TempData["Msg"] = "Option Added Successfully , Add Another Option";
model.OptionMaster.OptionValue = string.Empty;
model.OptionMaster.OptionRating = 0;
return View(model);
}
}
catch (Exception ex)
{
logger.LogError(ex);
}
finally
{
}
return View(model);
}
Here I am setting Option Value to empty and rating to Zero to take next entry , but on view it does not show empty and zero , on view it show previously filled value.
After Setting below code these two fields should be reset but they don't
model.OptionMaster.OptionValue = string.Empty;
model.OptionMaster.OptionRating = 0;
Is there any other way to set model object as null in Asp.net Core ?
This can happen because Razor helpers use values from ModelState, rather than the model itself. Your OptionValue is probably displayed using a helper, for example:
#Html.TextBoxFor(m => m.OptionMaster.OptionValue)
When you change model values within an action, you need remove the old values from ModelState before rendering the View.
The easiest way of doing this is to call ModelState.Clear()
model.OptionMaster.OptionValue = string.Empty;
model.OptionMaster.OptionRating = 0;
ModelState.Clear(); // ensure these changes are rendered in the View
return View(model);
The values displayed for bound form fields come from ModelState, which is composed based on values from Request, ViewData/ViewBag, and finally Model. After posting, obviously, you'll have values set in Request, which will therefore be the values in ModelState. It works this way, so that when there's a validation error and the user is returned to the form to correct their mistakes, the values they posted will be there for them to edit.
Long and short, you need to follow the PRG (Post-Redirect-Get) pattern. Essentially, after posting, you only return the view on error. If the post is successful, you redirect. This not only clears ModelState, but also prevents accidental re-posts if the user attempts to refresh the page.
If you want to take the user back to the same view, simply redirect to the same action, but you need to do a redirect, not return the view.

Value getting reset between ajax calls in Controller

In my controller, the method that returns the View also initializes a few values for some class-level properties:
private string igc = String.Empty;
private string igcCode = String.Empty;
private bool isSuggested = false;
public ActionResult Codes(Codes objCodes)
{
try
{
FillDropDowns(objCodes);
igc = String.Empty;
if (objICDCodes.FromWhere.IndexOf("MedicalInfo-Suggested") >= 0)
{
igc = objCodes.FromWhere.Remove(0, "MedicalInfo-Suggested-".Length);
igcCode = igc.Substring(0, igc.IndexOf("-")).Trim();
objCodes.ICGCode = igcCode;
isSuggested = true;
}
}
catch (Exception ex)
{
//logging error
ElmahLogUtility.ErrorException(ex);
}
return View(base.GetViewPath("Codes"), objCodes);
}
Additionally, there is this method which gets called to bind data to a grid on the page:
public JsonResult GetSelectedCodesInfo(List<SearchField> searchFields, GridDataSourceRequest request)
{
//creating the instance of DataSourceResult.
DataSourceResult dataSourceResult = null;
try
{
// Creating the instance of CommonBLL to load the values.
CommonBLL objCommonBLL = new CommonBLL();
if (isSuggested)
{
searchFields.Add(new SearchField() { ElementName = "aIGCode", Value = igcCode });
searchFields.Add(new SearchField() { ElementName = "aFor", Value = "EtiologicDiagnosis" });
}
// Getting the Codes information and storing in the DataSource Result.
dataSourceResult = objCommonBLL.GetSelectedCodesInfo(searchFields, request);
}
catch (Exception ex)
{
//Logging the Exception
ElmahLogUtility.ErrorException(ex);
}
// Returning the Result.
return Json(dataSourceResult, JsonRequestBehavior.AllowGet);
}
isSuggested gets set to true when the View is created, but when the data is bound to the grid isSuggested is set to false for some reason.
My grid is defined in a Razor view like so:
#Html.Grid("CodesSelectionGrid").ReadSource("Controller", "GetSelectedCodesInfo").OnGridDataBound("AssignCodeValues").Lazyload(true).EnableGrouping(false).EnableSorting(true).PageSize(10).Height("390px").Width("610px").EnablePaging(true).EnableFiltering(false).EnableMultiSelect(true).SelectionMode(SelectionMode.Single, "GetSelectedCodeDetails").RowSelection(GridRowSelection.None).ShowToolBar(true).SelectionCSSClass("icd-editable-cell").PageButtonCount(3)
That .ReadSource("Controller", "GetSelectedCodesInfo") bit is what refers to the Controller and the method on the controller to call. So, it's calling the second snippet of code above.
I must be accessing two separate instances of my Controller class, but I do not know how to solve this problem. How can I do this? How could I have my grid pass a reference of the Codes object? Then I could just get the values from there for the grid...
This is the expected behavior. isSuggested is a class level variable. Every time you make an Http request, a new instance of your controller will be created. That means the variable will be initialized to false. Remember, Http is Stateless :)
If you want to persist a variable value between multiple http calls, you need to persist it. You have different options like
Persist to a database table and read from that in the second call
Write to a file in disk and read from that in the second call
Save to user session and read from that in the second call

Page redirect for incorrect Razor view loading

I am working on error handling and user experience for a single page application. I am using razor views and have everything hooked up, but want to account for any possibility. If the model that is passed to the view doesn't have an expected value, it throws a generic error. .
I would like to be able to reroute the page to a more friendly error page, with some detail about the issue, however I haven't been able to figure out a way to catch a bad model between it being sent to the view and the page attempting to load it.
My question is, is there a way to recognize when a razor view will throw this error before it happens? and if so, how can I intercept it and reroute to an error page?
As requested, Editing to show the controller object.
public ActionResult GetStoreRequests(string storeId, string storeName)
{
var requestListCustomRenderer = new RequestListCustomRenderer();
var storeRequestRenderedResult = new StoreRequestsRenderedResult();
var storeRequestResponse = _repository.GetStoreRequestInfo(storeId);
StoreRequestWithStoreName dto = _repository.GetStoreRequests(storeRequestResponse, storeId, storeName);
storeRequestRenderedResult.ChargeOffCount = dto.StoreRequestList.ChargeOffListRemovalList.Count();
storeRequestRenderedResult.ExtensionCount = dto.StoreRequestList.AgreementExtensionList.Count();
storeRequestRenderedResult.InventoryCount = dto.StoreRequestList.InventoryExchangeList.Count();
storeRequestRenderedResult.StoreId = dto.StoreId;
storeRequestRenderedResult.StoreName = dto.StoreName;
storeRequestRenderedResult.RenderedStoreRequests = requestListCustomRenderer.RenderStoreRequests(dto);
return PartialView("ApprovalView", storeRequestRenderedResult);
}
Don't you want just try/catch your PartialView?
Like this:
try
{
PartialViewResult res = PartialView("ApprovalView", storeRequestRenderedResult);
return res;
}
catch (Exception ex)
{
return RedirectToAction("Error", "Home");
}

MVC + returning different PartialView on condition

Not sure if this is the best approach in MVC but how do I return views on condition, let's say if I want to return another view which displays some error message if my 'fbUID' is missing, please kindly assist. Thanks.
public PartialViewResult GetCredentials(string facebookUID, string facebookAccessTok)
{
string fbUID = facebookUID;
if (fbUID != null)
{
// Request fb profile pic
var rawImg = new Bitmap(ImageHelper.requestBitmapImage(fbUID));
var processblurredImg = new Bitmap(rawImg);
var gb = new GaussianBlur();
for (int i = 0; i < 8; i++)
{
gb.ApplyInPlace(processblurredImg);
}
// Download it to local drive / server
string uploadPath = Server.MapPath("~/upload");
string fullPath = uploadPath + "\\ProfilePic.png";
if (!Directory.Exists(uploadPath))
{
Directory.CreateDirectory(uploadPath);
}
if (uploadPath != null)
{
ImageHelper.savePng(fullPath, processblurredImg, 500L);
}
return PartialView("BlurredPhoto");
}
return PartialView("TestPartialView"); //if fbUID is null
}
Have a look at action filters. These allow you to install a class via an attribute on your controller method which intercepts the call before your method runs. You can do this kind of basic checking here and return a standard error handler result from here.
ASP.NET MVC has a built-in HandleErrorFilterAttribute that helps you to return error views if some errors occured in action or other filters. The built-in HandleError filter returns view not a partial view so you may have to create a custom one to return a partial view. The idea is you have to throw some custom exception from your action if fbUID is null and the custom handle error filter returns a partial view if it handles that exception.
I suggest going for a custom handle error filter approach only if you see this functionality in many places else it's more work for a simple thing!

Categories