i'm working on a mvc project and i want to display a sponsorimage on each page.
But i'm having difficulties with showing them into the shared layout page that is rendered on with each view.
I've created a function in my Domain service class where i search the student's school, because the school is linked to a country, not the student.
When i got that countryId, i search through the countries of each Advert where the countryId is equal to the school's countryId. When that's the case, i look for the sponsor of that particular advert, put them into a SponsorList, select a random sponsor from that SponsorList and return the SponsorCompany (because i renamed each sponsorimage to the companyname).
Now i want to call that function into the shared layout, so every time the pages renders, a random sponsorimage is showed for that particular student. But i dont know how to call that function because shared layout has no controller class.
public String advertsForCountry()
{
String studentSchool = finder.getLoggedStudent().SchoolId;
int studentCountry = db.Schools.Find(studentSchool).CountryId;
List<Sponsor> sponsorsForStudent = new List<Sponsor>();
List<Advert> adverts = db.Adverts.ToList();
foreach(Advert adv in adverts)
{
foreach(Country cntry in adv.Countries)
{
if(cntry.CountryId == studentCountry)
{
sponsorsForStudent.Add(adv.Sponsor);
}
}
}
Random random = new Random();
int randomSP = random.Next(0, sponsorsForStudent.Count()-1);
string sponsorAdvert = sponsorsForStudent.ElementAt(randomSP).SponsorCompany;
return sponsorAdvert;
}
Sorry English is not my native language.
To expand upon #SLaks suggestion;
Create an action that is marked with a ChildActionOnlyAttribute (this prevents it from being called via a regular HTTP request). Here's an example from my website:
[HttpGet]
[ChildActionOnly]
public ActionResult RandomQuote()
{
var model = _services.GetRandomQuote();
return PartialView("_QuoteOfTheMomentWidget", model);
}
This child action gets called in the _Layout via a simple #Html.Action("randomquote").
Create a controller action that returns a partial view.
public PartialViewResult SponsoredAdvert()
{
var model = new SponsoredAdverModel();
model.AdvertText = _domainService.advertsForCountry();
return PartialView("myView", model);
}
Place the method in a suitable controller (HomeController would make sense given that this is for your Layout.cshtml) and use RenderAction in your view:
#Html.RenderAction("MyAction", "MyController")
As you can see, RenderAction allows you to specify the controller, which means that you can use this within your Layout.cshtml even though it, in itself, is not associated with a specific controller.
Related
I need to build a ViewModel for my Layout's Web Application, I have tried this solution but it's not based on a URL's Id coming from the URL to generate the Layout's ViewModel.
I tried this but I first had the not existing empty controller error, then I tried to include the id as a parameter but I get the error "Object reference not set to an instance of an object." because the LayoutColorRGB is not set.
public MobileController(int id)
{
Event model = db.Events.Where(s => s.Id == id).FirstOrDefault();
LayoutVM = new LayoutVM()
{
EventId = model.Id,
LayoutColorRGB = model.LayoutColorRGB,
SponsorLogoLink = model.SponsorLogoLink,
SponsorLogoURL = model.SponsorLogoURL
};
ViewData["LayoutVM"] = LayoutVM;
}
There are many cases need to extract data based on the request context and show something on layout pages. For example:
You may want to show logged-in user info
You may want to show number of visitors or online visitors
You you may want to show the current language and let the user to change the language.
You may want to load site menu or side-bar or footer from database
To do so you can consider the following points:
Partial View: You can create some small partial views for those parts having a specific model for each partial view and render them in the layout page.
Use context to get data: You can initialize the model by extracting information from Request, ViewContext, RouteData, ValueProvider and other context objects.
Access to data by HTML Helpers: You can create a HtmlHelper to get data from context and use the helper in the layout or partial views.
Access to data by dependency injection: You can define some services for extracting data and then inject those data to layout pages. In the service, you will initialize the model using context objects. If you are using ASP.NET CORE, this is a good way to go.
Access to data as property of base controller: You can have a property in the base controller and initialize it in constructor of the controller or in OnActionExecuting. Then in the layout, get the property by casting ViewContext.Controller to type of your base controller and read the property.
Access to data by ViewBag: You can initialize an instance of the model in constructor of a base controller or in OnActionExecuting method of the base controller and then put it in ViewBag. Then you can easily use it in view.
Layout Pages: Don't forget you can define different layout pages and use different layouts based on your requirement. You can set the layout in the action or in _ViewStart.
Example
Trying to resolve id in each request, means you need to have id as part of all requests or you need to know what should you do in absence of id. Considering this fact and to keep things simple for a minimal example, I'll define the following model and base controller and try to resolve id in OnActionExecuting method of the base controller and then will derive all my controllers which needs such behavior from this base controller.
You can do the same using an ActionFilter or a global action filter of the OnActionExecuting method of your controller.
Using the following code:
If you browse /home/index you will see a red bar at bottom of the page.
If you browse /home/index/1 you will see a blue bar at the bottom of the page
If you browse /home/index/2 you will see a green bar at the bottom of the page
Layout Model
public class LayoutViewModel
{
public int? Id { get; set; }
public string Color { get; set; }
}
Base Controller
public class BaseControllr : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Get the id from route
var id = int.TryParse(ValueProvider.GetValue("id")?.AttemptedValue, out var temp)
? temp : default(int?);
var model = new LayoutViewModel();
//Your logic to initialize the model, for example
model.Id = id;
if (model.Id == null)
model.Color = "FF0000";
else if (model.Id%2==0)
model.Color = "00FF00";
else
model.Color = "0000FF";
//Set ViewBag
ViewBag.MainLayoutViewModel = model;
base.OnActionExecuting(filterContext);
}
}
Home Controller
public class HomeController : BaseControllr
{
public ActionResult Index(int? id)
{
return View();
}
}
_Layout.cshtml
Then in the _Layout.cshtml, add the following code before closing <body/> tag for test:
#{
string color = ViewBag.MainLayoutViewModel?.Color;
int? id = ViewBag.MainLayoutViewModel?.Id;
}
<div style="background-color:##color;">
Id:#id
</div>
You should have a default value for each property of your layout. Then if your model doe not have some property you can use it from the default layout.
internal static readonly LayoutVM defaultLayout = new LayoutVM()
{
EventId = 0,
LayoutColorRGB = "#FFFFFF",
SponsorLogoLink = "AnyLink",
SponsorLogoURL = "AnyImageUrl"
};
public MobileController(int id)
{
Event model = db.Events.Where(s => s.Id == id).FirstOrDefault();
if (model == null)
{
ViewData["LayoutVM"] = defaultLayout;
return;
}
LayoutVM = new LayoutVM()
{
EventId = model.Id,
LayoutColorRGB = model.LayoutColorRGB ?? defaultLayout.LayoutColorRGB,
SponsorLogoLink = model.SponsorLogoLink ?? defaultLayout.SponsorLogoLink,
SponsorLogoURL = model.SponsorLogoURL ?? defaultLayout.SponsorLogoURL
};
ViewData["LayoutVM"] = LayoutVM;
}
I'm developing a Web Application by using ASP.Net MVC 5. My model is something similar to:
Person
- int ID
- string FullName
- int PersonTypeId
PersonType
- Id
- Name
- Description
I'm working on the "create new Person" page. I have created a ViewModel with the following structure:
public class SampleAddViewModel
{
public Person person;
public SelectList personTypes; // Used to populate the DropDown element.
}
My controller's GET method (to simply display the page):
// GET: Add new person
public ActionResult Add()
{
SampleAddViewModel savm = new SampleAddViewModel();
AddPersonViewModel.personTypes = new SelectList(PersonTypesEntity.GetAll(), "Id", "Name");
return View(savm);
}
In my controller's POST method (to store the created person) I would expect to just receive the Person model, and not the entire ViewModel. But on the View page I think it is only possible to declare an #model razon line, which I think it must be #model SampleAddViewModel ...
Would it be possible to, in the POST Add entry, have something similar to the following:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Add([Bind(Include = "ID, Name, PersonTypeId")] Person person)
{
if (ModelState.IsValid)
{
db.Add(person);
db.SaveChanges();
return RedirectToAction("Index");
}
x <- //Should I re-create the ViewModel in here?
return View(x);
}
Which would be the best way to address the problem? I'm also trying to avoid using ViewBag. Maybe the best way in fact is to re-send the entire ViewModel.
If you have any errors on the server side on the POST method then yes, you'd want to send the ViewModel back to the view.
Since you are only sending a Person instance into the controller action and your View is expecting an instance of SampleAddViewModel, you should create an instance of one of these and pass it to the View; After all, you're going to need to repopulate the personTypes dropdown with data again.
I have a problem with validation of one view with multiple ViewModels. My situation is, that I have one Basic form, which is same for lot of pages. By ID parameter, I render new external fields to this Basic form. These external fields are type of ActionResult, using own ViewModel and own Controller. In Main controller on Post action I want to control if ModelState.IsValid, but I have problem - it validate all ViewModels of all external fields, but I want to validate only ViewModel of active external fields (and Basic form too).
It looks like this:
ViewModel of all view models
public class AllFieldsVm
{
public BasicFormVm BasicFormVm { get; set; }
public ExternalFieldXyVm ExternalFieldXyVm { get; set; }
public AnotherExternalFieldVm AnotherExternalFieldVm { get; set; }
}
In controller of external fields I create new instance of AllFieldsVm and in this create new instance of ExternalFieldXyVm (if I need, I prefill these fields). This I render whitout layout like partial view (using #{Html.RenderAction("Action", "Controller", new {#someOptionalData = value});} ), when some condition is true.
In controller of Basic form on Post action I have something like this and I want to use something like this code if (ModelState.IsValid(model.BasicFormVm) && ModelState.IsValid(model.ExternalFieldXyVm)):
[POST("someurl-id{someId}")]
public ActionResult SaveFormData(int someId, AllFieldsVm model)
{
//Here I want something like
//if (ModelState.IsValid(model.BasicFormVm) && ModelState.IsValid(model.ExternalFieldXyVm)) or something like that...
var se = new SomeEntity();
se.property1 = model.property1;
se.property2 = model.property2;
using (var dbc = _db.Database.BeginTransaction())
{
try
{
_db.Add(se);
_db.SaveChanges();
//My Condition - when save external data
if (someId == (int) MovementTypes.SomeEnumInt)
{
var rd = new ExternalFieldEntity
{
PropertyA = se.property0,
PropertyB = Convert.ToDateTime(model.ExternalFieldXyVm.SomeExternalFieldName)
};
_db.Add(rd);
_db.SaveChanges();
}
dbc.Commit();
}
catch (Exception)
{
dbc.Rollback();
}
}
return RedirectToAction("Action", "Controller");
}
So, my question is, how can I validate ExternalFieldXyVm separatly based on some conditions?
Is it possible, or I have to create all own validators, without using basic DataAnnotations or FluentValidation? I have no experience with these types of forms, so please be patient...
Thanks to all for help!!
Great, I got it. I play with this for two days, don't know how it is possible that I didn't see that.
Result is: When view with own view model which is included in main viewmodel, isn't rendered into view, this viewmodel is not validate on post action. So my Basic form is validate everytime, and ExternalFields are validate only when are rendered. So sorry, for so stupid question....
I have models (POCO entities) like Student, Course, Standard etc. I have corresponding controllers such as StudentController etc. I have a view Index for each model which displays the list of all the corresponding entities in DB. For example, StudentController.Index() returns the /Student/Index view. However, if there are no Student records in the DB, instead of returning the Index view , I redirect to the Empty action method of the Navigation controller, i.e. NavigationController.Empty(), which returns the /Navigation/Empty view. This is done for all model entity classes.
Now, on the empty page, I wish to have a hyperlink to go back to the previous page. So I created an action method called GoBack() in the NavigationController class, in which I redirect to the previous view. But how can I access the information about what the previous page was in this action method? Or is there a better way to do this? I do not want to use the back button.
As far as I'm concerned there are a couple of routes to take here. You could use sessions or the application cache to store a las visited page, and then get that page (by storing a route for instance) in the GoBack() action using a RedirectToAction.
But maybe a nicer and stateless aproach would be to render the hyperlink by having a view model having two properties for last used controller & action. Then you could pass these from the action result calling the /Navigation/Empty action (when there aren't any records).
ViewModel
public class NavigationVM
{
public string LastAction {get;set;}
public string LastController {get;set;}
}
Navigation Controller Action
public ActionResult Empty(string lastAction, string lastController)
{
var vm = new NavigationVM()
{
LastAction = lastAction,
LastController = lastController
}
return View(vm);
}
View
#model = Namespace.NavigationVM
#Html.ActionLink("LinkName", Model.LastAction, Model.LastController)
EDIT
If you then need to find out from where the students controller was called (in your example) you can go about this the same way. I.e.: Render the link to the StudentsController with extra route values.
StudentController:
public ActionResult Index(string lastAction, string lastController)
{
.... // no students
return RedirectToAction("Empty", "Navigation", new RouteValueDictionary(new { lastAction = "Index", lastController= "Student"}));
}
View with hyperlink to students controller (use the action and controller that rendered this view as lastAction and lastController respectively):
#Html.ActionLink("Get students", "Index", "Student", new { lastAction= "Index", lastController = "CallingController" }, null)
Warning: This is my first web app.
I have 4 models, views and controllers. Lets call them A, B, C, D(ex. ModelA, ControllerA, ViewA). They are all basic views with list scaffolding.
/ControllerA/Index
User starts at ViewA and Selects an the first item, which redirects the user to ViewB
/ControllerB/Function?Aid=1
ViewB shows another list based on Selection from ViewA. Then the user Selects again is is redirected to ViewC
/ControllerC/Function?Aid=1&Bid=2
ViewC shows another list based on Selections from ViewA and ViewB. Then the user Selects again is is redirected to ViewD.
/ControllerD/Function?Aid=1&Bid=2&Cid=3
ViewD shows another list based on Selections from ViewA, ViewB, and ViewC, Then the user Selects again.
At this point I would like to POST Aid, Bid, Cid, and Did and save them in my database. Ideally the user would click the link, the data would be posted and then the site would redirect the user back to the homepage. Should I create another model and controller to Handle the post? I thought about trying to do the POST from controllerD but that doesn't seem like the proper way to do this.
The msdn tutorials only show posting directly from a view with a strongly typed model. I kinda stuck and I would prefer not to make this a complete mess.
Edit for Code
Controller
public ActionResult myFunction(int Aid = 0, int Bid, int Cid)
{
//query D stuff here
if (D == null)
{
return HttpNotFound();
}
return View(D.ToList());
}
[HttpPost]
[InitializeSimpleMembership]
public ActionResult CreateQuote(int Aid, int Bid, int Cid, int Did)
{
Quote myQuote = new Quote();
myQuote.Customer_ID_FK = (int)Membership.GetUser().ProviderUserKey;
myQuote.A_ID_FK = Aid;
myQuote.B_ID_FK = Bid;
myQuote.C_ID_FK = Cid;
myQuote.D_ID_FK = Did;
if (ModelState.IsValid)
{
db.Quotes.Add(myQuote);
db.SaveChanges();
db.Quotes.Max();
int mymax = db.Quotes.Max(q => q.ID);
return RedirectToAction();
}
return View(D.ToList());
}
[HttpPost]
[InitializeSimpleMembership]
public ActionResult CreateQuote(Quote myQuote)
{
myQuote.Customer_ID_FK = (int)Membership.GetUser().ProviderUserKey;
if (ModelState.IsValid)
{
db.Quotes.Max();
int mymax = db.Quotes.Max(q => q.ID);
db.Quotes.Add(myQuote);
db.SaveChanges();
return RedirectToAction();
}
return View(D.ToList());
}
It usually makes sense to put your post handler in the controller it's related to. This isn't always the case, as sometimes it would make more sense to make a new controller to handle all posts related to a certain task. You should also understand the distinction between a method IN a controller, and a controller. A controller is just a class that inherits from System.Web.Mvc.Controller and can have methods just like any other class. A perfectly reasonable controller could look like this:
public class DController : Controller
{
//GET /d
public ActionResult Index()
{
//MyModel is a class that would contain the logic to display
//the selections from A, B, and C
var model = new MyModel();
return View(model);
}
//POST /d/saveresults
//We only want this method to accept POST
[HttpPost]
public ActionResult SaveResults(MyEntity data)
{
var model = new MyModel();
model.SaveResultsToDatabase(data);
return Redirect("/");
}
}
The important thing in a controller is to keep logical processing to a minimum. There's nothing wrong with having an if statement here and there, but the majority of your logic should be handled by your model. A controller is there primarily to pass data between your views and models.