treat AbsoluteUri C# - c#

I'm trying to figure out what is the best way to treat an URL from a MVC project.
As you will see I'm reading a XML file to distinguish witch client goes where into IIS.
My problem is, sometimes i get empty URl, sometimes clients gets redirect to a wrong path. As I'm using node.Value.Contains(ClientUrl) and .FirstOrDefault(). so whenever that happens I need to adapt the XML to it like adding /login# etc..
As all URL's are Kind similar I would like to treat the URL/path to each client an use node.Value.Equals(ClientUrl) instead.
Thanks all.
Controller
public ActionResult Index(string returnUrl)
{
try
{
Response.ClearContent();
Response.ClearHeaders();
Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(-1));
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();
var useragent = Request.UserAgent;
string UrlDoCliente = Request.Url.AbsoluteUri;
SysConfig _SysConfig = new SysConfig(ClientUrl);
var isAndroid = false;
int Id = SyscoopWebConfig.Empresa;
if (useragent != null)
{
if (useragent.ToLower().Contains("Android".ToLower()))
{
isAndroid = true;
}
else
{
isAndroid = Request.Browser.IsMobileDevice;
}
}
if (isAndroid)
{
string url = SysWebConfig.urlAppMobile;
if (url != "")
{
return Redirect(url);
}
}
if (!Request.IsAuthenticated)
{
Session["sesParam"] = null;
return Redirect(SysWebConfig.urlAppDesktop + "/login#");
}
}
catch (Exception e)
{
ViewBag.message = e.Message.ToString() + ". Url wsdl: " + SysWebConfig.Wsdl;
}
return View();
}
Reading xml
private static void readXMLConfig(string ClientUrl)
{
try
{
XmlDocument docX = new XmlDocument();
string sysWebPath = AppDomain.CurrentDomain.BaseDirectory;
docX.Load(WebPath + "/SysWebConfig.xml");
XElement xml = docX.ToXElement();
var config = (from nodes in xml.Descendants("client")
from node in nodes.Attributes("url")
where node.Value.Contains(ClientUrl)
select nodes).FirstOrDefault();
if (config != null)
{
_client = int.Parse(config.Element("clientId").Value);
_wsdl = config.Element("wsdl").Value;
_urlAppMobile = config.Element("urlAppMobile").Value;
_urlAppDesktop = config.Element("urlAppDesktop").Value;
}
}
catch (Exception)
{
throw new Exception('error reading xml file');
}
}
XML sample
<?xml version="1.0" encoding="utf-8"?>
<ClientConfig>
<SysWeb>
<client url="http://192.168.2.31/sysweb/client1">
<wsdl>http://192.168.2.25/wssysweb/sysweb.dll/soap/ISysWeb</wsdl>
<clientId>001</clientId>
<urlAppMobile>http://192.168.2.31/syswebmobile/AppClient1</urlAppMobile>
<urlAppDesktop>http://192.168.2.31/sysweb/AppClient1</urlAppDesktop>
</client>
<client url="http://192.168.2.31/sysweb/client1/login#">
<wsdl>http://192.168.2.25/wssysweb/sysweb.dll/soap/ISysWeb</wsdl>
<clientId>001</clientId>
<urlAppMobile>http://192.168.2.31/syswebmobile/AppClient1</urlAppMobile>
<urlAppDesktop>http://192.168.2.31/sysweb/AppClient1</urlAppDesktop>
</client>
<client url="http://192.168.2.31/sysweb/client2">
<wsdl>http://192.168.2.25/wssyweb/syspweb.dll/soap/ISysWeb</wsdl>
<clientId>002</clientId>
<urlAppMobile>http://192.168.2.31/syswebmobile/AppClient2</urlAppMobile>
<urlAppDesktop>http://192.168.2.31/sysweb/AppClient2</urlAppDesktop>
</client>
<client url="http://192.168.2.31/sysweb/client3">
<wsdl>http://192.168.2.25/wssyweb/syspweb.dll/soap/ISysWeb</wsdl>
<clientId>003</clientId>
<urlAppMobile>http://192.168.2.31/syswebmobile/AppClient3</urlAppMobile>
<urlAppDesktop>http://192.168.2.31/sysweb/AppClient3</urlAppDesktop>
</client>
</SysWeb>
</ClientConfig>

I´ve decided to go simple by using an array to set them all the same.
string ClientUrl = Request.Url.AbsoluteUri;
string[] vUrlArray = ClientUrl.Split('/');
if (vUrlArray.Count() >= 6)
{
ClientUrl = "";
for (int i = 0; i <= 4; i++)
{
ClientUrl = ClientUrl + vUrlArray[i] + "/";
}
ClientUrl = ClientUrl.Substring(0, UrlDoCliente.LastIndexOf("/"));
}

Related

Import a COM register dll in application in KTM

I create a dll it contains an entry point. The whole code is working fine. I build the dll, i created a console project that import the reference and so on.
My Issue concerns importing the dll into an application that requires the DLL to be registered as COM. Based on documentation i found, I used Regasm.exe:
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" "mydll.dll" /tlb /codebase
This generates a .tlb file, so far so good. I do also successfully import the tlb. My issue is that even if the dll is correctly found, I can't access to any methods of it.
[![enter image description here][1]][1]
here is a sample of code:
namespace mynamespace
{
public class AppManager
{
public Task<SoapResponse> SoapApiCaller(String url, String val1, String val2)
{
return Utils.queryResponse(url, val1, val2);
}
}
}
my utility class is just making the call. It should be splitted too. but that's not the point;
namespace mynamespace
{
public class Utils
{
public static async Task<SoapResponse> queryResponse(String url, String val1,
String val2)
{
SoapResponse soapResponse= new SoapResponse();
// check before values
bool isValid = true;
var exc = "";
if (url == null)
{
isValid = false;
exc += "L'url est requise\n";
}
if (val1 == null)
{
isValid = false;
exc += "Va\n";
}
if (val2 == null)
{
isValid = false;
exc += "val 2 required\n";
}
if (!isValid)
{
throw new Exception(exc);
}
// SOAP enveloppe
string xmlStr = "<?xml version=\"1.0\" encoding=\"utf-8\" ?> ...";
StringContent stringContent = new StringContent(xmlStr, Encoding.UTF8, "text/xml");
try
{
var client = new HttpClient();
SoapResponse soapResponse = new SoapResponse();
var response = await client.PostAsync(url, stringContent);
var soapResponse = await response.Content.ReadAsStringAsync();
var statusCode = (int)response.StatusCode;
XDocument document = XDocument.Parse(soapResponse);
if (statusCode == 200)
{
var WebServiceFirstResult =
document.Descendants().FirstOrDefault(p => p.Name.LocalName == "WebServiceFirstResult");
var WebServiceSecondResult =
document.Descendants().FirstOrDefault(p => p.Name.LocalName == "WebServiceSecondResult");
XText xText = (XText)WebServiceSecondResult.FirstNode;
var xStr = xText.ToString().Replace(">", ">").Replace("<", "<");
xStr = xStr
.Replace("]]>", "")
.Replace("<![CDATA[<", "");
string[] strArr = xStr.Split('<');
// debugger here !
for (var i = 0; i < strArr.Length; i++)
{
// nettoyer autour de "CDATA"
Console.WriteLine(strArr[i]);
Console.Write("");
// on splitte avec >
if (strArr[i].Split('>')[0] == "VAL1")
{
response.VAL1= strArr[i].Split('>')[1];
}
if (strArr[i].Split('>')[0] == "VAL2")
{
response.VAL2= strArr[i].Split('>')[1];
}
if (strArr[i].Split('>')[0] == "VAL3")
{
response.VAL3 = strArr[i].Split('>')[1];
}
soapResponse.message = "OK";
soapResponse.statusCode = (int)response.StatusCode;
soapResponse.response= response;
}
else
{
soapResponse.statusCode = (int)response.StatusCode;
if (document.Descendants().FirstOrDefault(p => p.Name.LocalName == "faultstring") != null)
{
var errMessageElt = document.Descendants().FirstOrDefault(p => p.Name.LocalName == "faultstring");
if (errMessageElt.Value != null)
{
int index = errMessageElt.Value.IndexOfAny(new char[] { '\r', '\n' });
string firstline = index == -1 ? errMessageElt.Value : errMessageElt.Value.Substring(0, index);
var exceptionMessage = "KO : erreur " + (int)response.StatusCode + " "+ firstline;
throw new Exception(exceptionMessage);
}
}
else
{
throw new Exception("KO : erreur " + (int)response.StatusCode + " " + soapResponse);
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw new Exception(e.Message);
}
return soapResponse;
}
}
}
the assembly information contain the following information:
[![enter image description here][2]][2]
[![enter image description here][3]][3]
It's using .NET Framework 4.8 and c# 8
[1]: https://i.stack.imgur.com/xjbeP.png
[2]: https://i.stack.imgur.com/j3wjq.png
[3]: https://i.stack.imgur.com/kVDFM.png

create a file and upload the chunks to the in team drive from C#

I Have to Upload the file to my teamdrive, the file has been created to team drive but I can not upload the file chunks to it. So, please help to solve it.
On Writing a Chunk I am facing the Error of "The remote server returned an error: (404) Not Found."
I am getting the Teamdrive ID and the File ID which has been created.
/*** Creation of a File to Team Drive ***/
f_ObjFile.TeamDriveId = "/*TeamDrive ID*/";
try
{
f_ObjNewFile.Parents = f_ObjFile.Parents; // f_ObjFile = <Team Driv ID>
f_ObjNewFile.Name = f_ObjFile.Name;
f_ObjNewFile.MimeType = f_ObjFile.MimeType;
f_ObjNewFile.TeamDriveId = f_ObjFile.TeamDriveId;
f_CreateRequest = GoogleHelper.InvokeApiCall(() => { return this.DriveServiceObj.Files.Create(f_ObjNewFile); }, this);
if (f_CreateRequest != null)
{
f_CreateRequest.SupportsTeamDrives = true;
f_CreateRequest.Fields = "*";
f_ObjNewFile = GoogleHelper.InvokeApiCall(() => { return f_CreateRequest.Execute(); }, this);
}
f_ObjDocumentItem = new DocumentItem(UserEmailID, f_ObjNewFile);
f_ObjDocumentItem.ItemID = f_ObjNewFile.Id;
string f_Url = GoogleHelper.CreateChunkURL("https://www.googleapis.com/upload/drive/v3/files/{0}?uploadType=resumable", f_ObjNewFile.Id);
f_ObjDocumentItem.ChunkUploadURL = InitiateResumeRequest(f_Url, f_ObjNewFile.Id);
}
catch(Exception ex) { }
finally
{
f_ObjNewFile = null;
f_CreateRequest = null;
}
/* Writing the chunks to the file in TeamDrive */
try
{
httpRequest = GoogleHelper.CreateHttpWebRequestObj(f_ObjChunkData.ChunkUploadURL,true);
httpRequest.Method = GoogleConstant.PATCH;
httpRequest.ContentLength = f_ObjChunkData.FileData.Length;
httpRequest.SendChunked = true;
httpRequest.Headers["Content-Range"] = "bytes " + f_ObjChunkData.StartOffset +
"-" +
f_ObjChunkData.EndOffset + "/" +
f_ObjChunkData.FileSize.ToString();
using (System.IO.Stream f_ObjHttpStream = GoogleHelper.InvokeApiCall(() => { return httpRequest.GetRequestStream(); }, this))
{
if (f_ObjHttpStream != null)
{
System.IO.MemoryStream f_ChunkStream = null;
f_ChunkStream = new System.IO.MemoryStream(f_ObjChunkData.FileData);
f_ChunkStream.CopyTo(f_ObjHttpStream);
f_ObjHttpStream.Flush();
f_ObjHttpStream.Close();
f_ChunkStream.Close();
f_ChunkStream = null;
}
}
using (HttpWebResponse httpResponse = GoogleHelper.InvokeApiCall(() => { return (HttpWebResponse)(httpRequest.GetResponse()); }, this))
{
if (httpResponse != null)
{
if (httpResponse.StatusCode == HttpStatusCode.OK)
{
httpResponse.Close();
}
}
}
}
catch (Exception ex) { }
In Followin Line :
string f_Url = GoogleHelper.CreateChunkURL("https://www.googleapis.com/upload/drive/v3/files/{0}?uploadType=resumable", f_ObjNewFile.Id);
Insted Of
"https://www.googleapis.com/upload/drive/v3/files/{0}?uploadType=resumable"
Use following URL :
https://www.googleapis.com/upload/drive/v3/files/{0}?uploadType=resumable&supportsTeamDrives=true
and its Done...
Now you can upload the chunks...

Validating digitaly signed PDF Spire.Pdf C#

So I am developing a web app that generates a PDF contract from a partial view, and then validates the digital signiture. I came accross an example here . The problem is that an exception is thrown when validating the signiture and for the life of me I cant figure out why...
Here is the code :
public async Task<ActionResult> Upload(HttpPostedFileBase FileUpload)
{
ActionResult retVal = View();
AspNetUser user = DbCtx.AspNetUsers.Find(User.Identity.GetUserId());
bool signitureIsValid = false;
string blobUrl = string.Empty;
if (FileUpload != null && FileUpload.ContentLength > 0)
{
string fileName = Guid.NewGuid().ToString() + RemoveAllSpaces(FileUpload.FileName);
string filePath = Path.Combine(System.Web.Hosting.HostingEnvironment.MapPath("~/Content/pdfs"), fileName);
FileUpload.SaveAs(filePath);
List<PdfSignature> signatures = new List<PdfSignature>();
using (var doc = new PdfDocument(filePath))
{
var form = (PdfFormWidget) doc.Form;
int count = 0;
try
{
count = form.FieldsWidget.Count;
}
catch
{
count = 0;
}
for (int i = 0; i < count; ++i)
{
var field = form.FieldsWidget[i] as PdfSignatureFieldWidget;
if (field != null && field.Signature != null)
{
PdfSignature signature = field.Signature;
signatures.Add(signature);
}
}
}
PdfSignature signatureOne = signatures[0];
try
{
signitureIsValid = signatureOne.VerifySignature(); // HERE SHE BLOWS !
if (signitureIsValid)
{
blobPactUrl = await BlobUtil.BasicStorageBlockBlobOperationsAsync(System.IO.File.ReadAllBytes(filePath));
if (!string.IsNullOrEmpty(blobPactUrl))
{
ApplicantInfo info = DbCtx.ApplicantInfoes.FirstOrDefault(x => x.UserId == user.Id);
info.URL = blobUrl;
info.SignatureIsValid = true;
info.ActivationDate = DateTime.Now;
info.ActiveUntill = DateTime.Now.AddYears(1);
DbCtx.Entry(info).State = System.Data.Entity.EntityState.Modified;
DbCtx.SaveChanges();
retVal = RedirectToAction("Publications");
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
System.IO.File.Delete(filePath);
}
}
return retVal;
}
Here is an image of
what it looks like when I'm debuging:
I have checked the signiture and it is valid and cerified... I know I'm missing something basic here... Please help me internet!
Just noticed that I posted this. . . Turns out that the problem was due to bug in the package itself. Upon asking the lovely people at E-Iceblue, the bug was recreated, solved and a new version of Spire.PDF was up on nuget within a week.
great job E-Iceblue, it worked fine :)

Trying to read web.config file for an IIS site from a Windows service

I am trying to look for a special web.config file for a web site installed on a local IIS. I do this search from a Windows service. I do the following:
using (ServerManager serverManager = new ServerManager())
{
for (int r = 0; r < serverManager.Sites.Count; r++)
{
string strSiteName = serverManager.Sites[r].Name;
ApplicationCollection arrApps = serverManager.Sites[r].Applications;
for (int a = 0; a < arrApps.Count; a++)
{
Microsoft.Web.Administration.Application aa = arrApps[a];
foreach (VirtualDirectory vd2 in aa.VirtualDirectories)
{
string strPhysPath = Environment.ExpandEnvironmentVariables(vd2.PhysicalPath);
int rr = 0;
try
{
Configuration cnfg = serverManager.GetWebConfiguration(strSiteName, strPhysPath);
if (cnfg != null)
{
string swww = getWebConfigGeneralParamValue(cnfg, "SpecNodeName");
}
}
catch
{
//Error
}
}
}
}
Where,
public static string getWebConfigGeneralParamValue(Configuration config, string strParamKey)
{
string strRes = null;
try
{
ConfigurationSection configSection1 = config.GetSection("configuration");
if (configSection1 != null)
{
ConfigurationElement configGeneralParams = configSection1.GetChildElement("GeneralParams");
if (configGeneralParams != null)
{
ConfigurationElementCollection configCol = configSection1.GetCollection();
if (configCol != null)
{
strRes = configCol[strParamKey].ToString();
}
}
}
}
catch(Exception ex)
{
strRes = null;
}
return strRes;
}
and the web.config file that should be recognized by this script is something like this:
<?xml version="1.0"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<!-- regular ASP.NET config stuff -->
<GeneralParams>
<param key="SpecNodeName" value="Special Node Value"/>
</GeneralParams>
</configuration>
But what I get is that config.GetSection("configuration"); throws this exception:
{"Filename:
\\?\C:\inetpub\wwwroot\C:\Users\Dev\Desktop\CSharp\MyTestWebApp\MyTestWebApp\web.config\r\nError:
The configuration section 'configuration' cannot be read because it is
missing a section declaration\r\n\r\n"}
Any idea how to make it work?
You need not to target configuration, you need to target that particular node from where you need to get your data. here is the example.
using(ServerManager mgr = ServerManager.OpenRemote("Some-Server")) {
Configuration config = mgr.GetWebConfiguration("site-name", "/test-application");
ConfigurationSection appSettingsSection = config.GetSection("appSettings");
ConfigurationElementCollection appSettingsCollection = appSettingsSection.GetCollection();
ConfigurationElement addElement = appSettingsCollection.CreateElement("add");
addElement["key"] = #"NewSetting1";
addElement["value"] = #"SomeValue";
appSettingsCollection.Add(addElement);
serverManager.CommitChanges();
}
I am feeling, if you have web.config why you want to use ServerManager.
Take a look at below code, this I have been using in my code and it works as a charm.
What it does is it loads web.config in readable format.
var assemblyPath = Path.GetDirectoryName(typeof (ConfigurationTests).Assembly.Location);
string webConfigPath = MyPath;
string directory = System.IO.Path.GetFullPath(Path.Combine(assemblyPath,webConfigPath));
Console.WriteLine(directory);
Assert.That(Directory.Exists(directory));
VirtualDirectoryMapping vdm = new VirtualDirectoryMapping(directory, true);
WebConfigurationFileMap wcfm = new WebConfigurationFileMap();
wcfm.VirtualDirectories.Add("/", vdm);
System.Configuration.Configuration webConfig = WebConfigurationManager.OpenMappedWebConfiguration(wcfm, "/");
var connectionString = webConfig.ConnectionStrings.ConnectionStrings["ConnectionString"].ConnectionString;
Console.WriteLine("Connection String:");
Console.WriteLine(connectionString);
Console.WriteLine("AppSettings:");
foreach (var key in webConfig.AppSettings.Settings.AllKeys)
{
Console.WriteLine("{0} : {1}", key, webConfig.AppSettings.Settings[key].Value);
}

WCF Streaming File Transfer ON .NET 4

I need a good example on WCF Streaming File Transfer.
I have found several and tried them but the posts are old and I am wokding on .net 4 and IIS 7 so there are some problems.
Can you gives me a good and up-to-date example on that.
The following answers detail using a few techniques for a posting binary data to a restful service.
Post binary data to a RESTful application
What is a good way to transfer binary data to a HTTP REST API service?
Bad idea to transfer large payload using web services?
The following code is a sample of how you could write a RESTful WCF service and is by no means complete but does give you an indication on where you could start.
Sample Service, note that this is NOT production ready code.
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class FileService
{
private IncomingWebRequestContext m_Request;
private OutgoingWebResponseContext m_Response;
[WebGet(UriTemplate = "{appName}/{id}?action={action}")]
public Stream GetFile(string appName, string id, string action)
{
var repository = new FileRepository();
var response = WebOperationContext.Current.OutgoingResponse;
var result = repository.GetById(int.Parse(id));
if (action != null && action.Equals("download", StringComparison.InvariantCultureIgnoreCase))
{
response.Headers.Add("Content-Disposition", string.Format("attachment; filename={0}", result.Name));
}
response.Headers.Add(HttpResponseHeader.ContentType, result.ContentType);
response.Headers.Add("X-Filename", result.Name);
return result.Content;
}
[WebInvoke(UriTemplate = "{appName}", Method = "POST")]
public void Save(string appName, Stream fileContent)
{
try
{
if (WebOperationContext.Current == null) throw new InvalidOperationException("WebOperationContext is null.");
m_Request = WebOperationContext.Current.IncomingRequest;
m_Response = WebOperationContext.Current.OutgoingResponse;
var file = CreateFileResource(fileContent, appName);
if (!FileIsValid(file)) throw new WebFaultException(HttpStatusCode.BadRequest);
SaveFile(file);
SetStatusAsCreated(file);
}
catch (Exception ex)
{
if (ex.GetType() == typeof(WebFaultException)) throw;
if (ex.GetType().IsGenericType && ex.GetType().GetGenericTypeDefinition() == typeof(WebFaultException<>)) throw;
throw new WebFaultException<string>("An unexpected error occurred.", HttpStatusCode.InternalServerError);
}
}
private FileResource CreateFileResource(Stream fileContent, string appName)
{
var result = new FileResource();
fileContent.CopyTo(result.Content);
result.ApplicationName = appName;
result.Name = m_Request.Headers["X-Filename"];
result.Location = #"C:\SomeFolder\" + result.Name;
result.ContentType = m_Request.Headers[HttpRequestHeader.ContentType] ?? this.GetContentType(result.Name);
result.DateUploaded = DateTime.Now;
return result;
}
private string GetContentType(string filename)
{
// this should be replaced with some form of logic to determine the correct file content type (I.E., use registry, extension, xml file, etc.,)
return "application/octet-stream";
}
private bool FileIsValid(FileResource file)
{
var validator = new FileResourceValidator();
var clientHash = m_Request.Headers[HttpRequestHeader.ContentMd5];
return validator.IsValid(file, clientHash);
}
private void SaveFile(FileResource file)
{
// This will persist the meta data about the file to a database (I.E., size, filename, file location, etc)
new FileRepository().AddFile(file);
}
private void SetStatusAsCreated(FileResource file)
{
var location = new Uri(m_Request.UriTemplateMatch.RequestUri.AbsoluteUri + "/" + file.Id);
m_Response.SetStatusAsCreated(location);
}
}
Sample Client, note that this is NOT production ready code.
// *********************************
// Sample Client
// *********************************
private void UploadButton_Click(object sender, EventArgs e)
{
var uri = "http://dev-fileservice/SampleApplication"
var fullFilename = #"C:\somefile.txt";
var fileContent = File.ReadAllBytes(fullFilename);
using (var webClient = new WebClient())
{
try
{
webClient.Proxy = null;
webClient.Headers.Add(HttpRequestHeader.ContentMd5, this.CalculateFileHash());
webClient.Headers.Add("X-DaysToKeep", DurationNumericUpDown.Value.ToString());
webClient.Headers.Add("X-Filename", Path.GetFileName(fullFilename));
webClient.UploadData(uri, "POST", fileContent);
var fileUri = webClient.ResponseHeaders[HttpResponseHeader.Location];
Console.WriteLine("File can be downloaded at" + fileUri);
}
catch (Exception ex)
{
var exception = ex.Message;
}
}
}
private string CalculateFileHash()
{
var hash = MD5.Create().ComputeHash(File.ReadAllBytes(#"C:\somefile.txt"));
var sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("x2"));
}
return sb.ToString();
}
private void DownloadFile()
{
var uri = "http://dev-fileservice/SampleApplication/1" // this is the URL returned by the Restful file service
using (var webClient = new WebClient())
{
try
{
webClient.Proxy = null;
var fileContent = webClient.DownloadData(uri);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}

Categories